create-t3-turbo icon indicating copy to clipboard operation
create-t3-turbo copied to clipboard

feat: implemented docker support for nextjs app

Open EkkoKo opened this issue 3 years ago • 11 comments

EkkoKo avatar Mar 31 '23 13:03 EkkoKo

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

A member of the Team first needs to authorize it.

vercel[bot] avatar Mar 31 '23 13:03 vercel[bot]

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

ChristianKuri avatar May 15 '23 19:05 ChristianKuri

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

EkkoKo avatar May 15 '23 19:05 EkkoKo

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

clicktodev avatar Jul 16 '23 22:07 clicktodev

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?

akutruff avatar Oct 24 '23 18:10 akutruff

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, image

albertilagan avatar Oct 24 '23 19:10 albertilagan

@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;

akutruff avatar Oct 25 '23 12:10 akutruff

@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 avatar Oct 25 '23 13:10 albertilagan

@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?

adamspotlite avatar Mar 28 '24 06:03 adamspotlite

@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.

albertilagan avatar Mar 28 '24 17:03 albertilagan

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

jpainam avatar Sep 12 '25 01:09 jpainam