feat: implemented docker support for nextjs app
@EkkoKo is attempting to deploy a commit to the t3-oss Team on Vercel.
A member of the Team first needs to authorize it.
I havent tested it, but im looking forward using it. I think its also a good idea to include a docker compose file.
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
Any plans to merge this? What's blocking this from getting approved.
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?
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 [email protected]
RUN npm i -g [email protected]
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,
@albertilagan Thanks for the reply! A couple of things I found that you may want to add:
- Install
libc6-compatto alpine base image. (see comment inline) - Can copy
turbo.jsonand useturbo run buildwith ellipses to do the build with dependencies. - Added a commented out line to copy an
.envfile in case the build is not being used withdocker composeFull 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;
@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. :)
@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 great work! are you checking the
envat 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
envbefore 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.
Hi. Anyone able to make this work? i'm facing
=> ERROR [installer 10/10] RUN CI=true SKIP_ENV_VALIDATION=true turbo run build --filter=@acme/nextjs...
....
0.783 @acme/validators:build:
1.983 @acme/db:build: error TS2688: Cannot find type definition file for 'minimatch'.
1.983 @acme/db:build: The file is in the program because:
1.983 @acme/db:build: Entry point for implicit type library 'minimatch'
1.997 @acme/validators:build: error TS2688: Cannot find type definition file for 'minimatch'.
I installed @types/minimatch as a devDependencies at the root, but the issue persist.
I'm not particularly using it