Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implemented docker support for nextjs app #255

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

EkkoKo
Copy link

@EkkoKo EkkoKo commented Mar 31, 2023

No description provided.

@EkkoKo EkkoKo reopened this Mar 31, 2023
@vercel
Copy link

vercel bot commented Mar 31, 2023

@EkkoKo is attempting to deploy a commit to the t3-oss Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Member

@juliusmarminge juliusmarminge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tested it out myself yet but got some initial thoughts

apps/nextjs/next.config.mjs Outdated Show resolved Hide resolved
Comment on lines +30 to +34
"@types/node": "^18.15.11",
"@types/react": "^18.0.31",
"tailwindcss": "^3.3.1",
"postcss": "^8.4.21",
"autoprefixer": "^10.4.14",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im no docker expert but why do these need to be production deps all of a sudden?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you run pnpm install --prod it only installs dependencies and when nextjs is trying to create to build it fails because it looks for those dependencies.

@@ -12,11 +12,11 @@
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"prisma": "^4.12.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here - prisma is a devDep, why does it need to be a prod dep now?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the turbo.json we declare that before build it should run db:generate, db:generate runs prisma and because we only install dependencies I had to add it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend leaving the devDependencies and prod dependencies as they are, but limit the number of packages installed with pnpm install --filter @acme/nextjs...

Copy link

@albertilagan albertilagan Jul 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as you're using output: "standalone" devDep will not get included on the final image, so imo you don't need --prod when running pnpm install, specially since you're doing prune anyway.

@ChristianKuri
Copy link

I havent tested it, but im looking forward using it. I think its also a good idea to include a docker compose file.

@EkkoKo
Copy link
Author

EkkoKo commented May 15, 2023

I havent tested it, but im looking forward using it. I think its also a good idea to include a docker compose file.

I have included a docker-compose file, you should definitely check it out :)
It is also mentioned it in the README

@hichemfantar
Copy link
Contributor

Any plans to merge this?
What's blocking this from getting approved.

@akutruff
Copy link

I've burned all day trying to get docker setup and it looks like everything out there is either broken or results in an image size of a GB in size. Anyone have a working docker setup that doesn't alter dependencies?

@albertilagan
Copy link

albertilagan commented Oct 24, 2023

I've burned all day trying to get docker setup and it looks like everything out there is either broken or results in an image size of a GB in size. Anyone have a working docker setup that doesn't alter dependencies?

Here,

ARG NODE_VERSION=18.17.1
ARG ALPINE_VERSION=3.17

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS base

RUN npm i -g pnpm@8.7.5
RUN npm i -g turbo@1.10.14

FROM base AS builder

# Set working directory
WORKDIR /app
COPY . .
RUN turbo prune --scope=test-app --docker

# Add lockfile and package.json's of isolated subworkspace
FROM base AS installer
WORKDIR /app

ARG TURBO_TEAM
ARG TURBO_TOKEN

ENV TURBO_TEAM=$TURBO_TEAM
ENV TURBO_TOKEN=$TURBO_TOKEN

# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ ./
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile

# Build the project
COPY --from=builder /app/out/full/ ./
RUN CI=true SKIP_ENV_VALIDATION=1 pnpm build --filter=test-app

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS runner
WORKDIR /app

COPY --from=installer /app/apps/test-app/next.config.mjs .
COPY --from=installer /app/apps/test-app/package.json .

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer /app/apps/test-app/.next/standalone ./
COPY --from=installer /app/apps/test-app/.next/static ./apps/test-app/.next/static
COPY --from=installer /app/apps/test-app/public ./apps/test-app/public

CMD node apps/test-app/server.js

you need standalone build for this to work,
image

@akutruff
Copy link

@albertilagan Thanks for the reply! A couple of things I found that you may want to add:

  • Install libc6-compat to alpine base image. (see comment inline)
  • Can copy turbo.json and use turbo run build with ellipses to do the build with dependencies.
  • Added a commented out line to copy an .env file in case the build is not being used with docker compose Full example in the next.js repo here
FROM node:18-alpine AS base
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat

FROM base AS builder
WORKDIR /app

RUN npm install -g turbo
COPY . .
RUN turbo prune --scope=@acme/nextjs --docker

FROM base as installer
WORKDIR /app

RUN npm install -g pnpm
RUN npm install -g turbo

ARG TURBO_TEAM
ARG TURBO_TOKEN

ENV TURBO_TEAM=$TURBO_TEAM
ENV TURBO_TOKEN=$TURBO_TOKEN

# First install dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile

# Build the project and its dependencies
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json

## This would be useful for browser environment variables that are actually baked at build time and you aren't passing them in otherwise.
# COPY .env.production .env.production  
RUN CI=true SKIP_ENV_VALIDATION=true turbo run build --filter=@acme/nextjs...

FROM base AS runner
WORKDIR /app

EXPOSE 3000
ENV PORT 3000

ENV NODE_ENV production
ENV HOSTNAME localhost

# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=installer /app/apps/nextjs/next.config.mjs .
COPY --from=installer /app/apps/nextjs/package.json .

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/public ./apps/nextjs/public

CMD node apps/nextjs/server.js

For anyone reading this out of context, you also want a .dockerignore in your root

node_modules

# This line is **EXTREMELY** important
# https://github.com/vercel/turbo/issues/1997#issuecomment-1273565773
**/node_modules

You also want standalone as @albertilagan pointed out. Here's a full file that supports multi package:

import "./src/env.mjs";
import "@acme/auth/env.mjs";

/** @type {import("next").NextConfig} */
const config = {
  reactStrictMode: true,
  /** Enables hot reloading for local packages without a build step */
  transpilePackages: ["@acme/api", "@acme/auth", "@acme/db"],
  /** We already do linting and typechecking as separate tasks in CI */
  eslint: { ignoreDuringBuilds: true },
  typescript: { ignoreBuildErrors: true },
  output: 'standalone',
};

export default config;

@albertilagan
Copy link

albertilagan commented Oct 25, 2023

@akutruff

You're welcome :) Yeah feel free to extend it to whatever you need.
we re-use image for different environment hence we're not adding .env files on build time,
- server env are injected when running the image
- browser env are injected on runtime as well using docker ENTRYPOINT with custom env substitution script that modifies the bundle output.

I can't really share the script, but we utilize printenv and envsubst, you can probably figure out the rest. :)

@adamspotlite
Copy link

@albertilagan great work! are you checking the env at all before the injection? We accidentally shipped an image that ultimately didn't have the proper env vars, but it wasn't picked up until it was already serving traffic..

any chance you know how to check the env before the build to validate it? I currently have the docker setup working and deploying properly but would love to add a pre-build step to ensure that the env that I will be applying to the container will be valid. Any ideas?

@albertilagan
Copy link

@albertilagan great work! are you checking the env at all before the injection? We accidentally shipped an image that ultimately didn't have the proper env vars, but it wasn't picked up until it was already serving traffic..

any chance you know how to check the env before the build to validate it? I currently have the docker setup working and deploying properly but would love to add a pre-build step to ensure that the env that I will be applying to the container will be valid. Any ideas?

we are not, doesn't really apply to us because we have a good k8s config setup and separation of environments,
all we did on our script is filter out NEXT_PUBLIC

PUBLIC_VARS=$(printenv | grep '^NEXT_PUBLIC' | awk -F= '{print $1}' | sed 's/^/\$/g' | paste -sd "," -)

and apply the envsubst on those as mentioned above.

COPY --from=builder /app/out/json .
COPY --from=builder /app/out/pnpm-lock.yaml\* ./

RUN yarn global add pnpm && pnpm fetch --prod && pnpm install -r --offline --prod

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why mix with yarn and pnpm ??

ENV SKIP_ENV_VALIDATION true

COPY .gitignore .gitignore
COPY --from=builder /app/tsconfig.json ./tsconfig.json

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will not work it on /app/out/tsconfig.ts not in /app/tsconfig.ts i get error as not found when i try run at in server
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants