Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
# .dockerignore is used to exclude files and directories from being copied into the Docker image during the build process. This helps to reduce the size of the image and improve build times by only including necessary files.
# keep it at the root of the project to ensure that we don't copy unnecessary files into the Docker image.

node_modules
**/.turbo
# git (disable if needed in the container)
.git

# dependencies
**/node_modules

# caches
**/.cache
**/tsconfig.tsbuildinfo
**/tsconfig.*.tsbuildinfo
**/.eslintcache

# package managers
**/.yarn/*
!**/.yarn/patches
!**/.yarn/releases
!**/.yarn/plugins
.pnp.*

# testing
**/coverage
**/.out/

# Build directories
**/apps/*/.next
**/packages/*/dist
**/packages/*/docs
**/.eslintcache

# Misc
.DS_Store
*.pem

# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# IDE
.idea/*
.project
.classpath
*.launch
*.sublime-workspace
.vscode/


Comment on lines 1 to +52
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 .turbo cache directory no longer excluded

The previous .dockerignore explicitly excluded **/.turbo (Turborepo's local build-cache). This entry was removed in the new version. While the newly generated .turbo-pruned output is intentional, the .turbo cache directories on the host can be large and are never needed inside the image. Omitting this exclusion will increase Docker build-context transfer size on incremental builds.

Consider re-adding the exclusion:

# turbo cache
**/.turbo

The same entry is also missing from examples/apps/nextjs-app/docker/.dockerignore.

2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
_release

# Turbo pruned files
out
.turbo-pruned

# local env files (following nextjs convention)

Expand Down
52 changes: 45 additions & 7 deletions examples/apps/nextjs-app/docker/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
# this file should won't be read unless it exists in the
# context directory specified in the docker compose file

node_modules
#**/.turbo
#**/.cache
#**/apps/*/.next
#**/packages/*/dist
#**/packages/*/docs
#**/.eslintcache
# git (disable if needed in the container)
.git

# dependencies
**/node_modules

# caches
**/.cache
**/tsconfig.tsbuildinfo
**/tsconfig.*.tsbuildinfo
**/.eslintcache

# package managers
**/.yarn/*
!**/.yarn/patches
!**/.yarn/releases
!**/.yarn/plugins
.pnp.*

# testing
**/coverage
**/.out/

# Build directories
**/apps/*/.next
**/packages/*/dist
**/packages/*/docs

# Misc
.DS_Store
*.pem

# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# IDE
.idea/*
.project
.classpath
*.launch
*.sublime-workspace
.vscode/

21 changes: 10 additions & 11 deletions examples/apps/nextjs-app/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ RUN TURBO_VERSION=$(cat package.json | jq '.devDependencies["turbo"]' -r) npm i
COPY --link . .

# https://turbo.build/repo/docs/handbook/deploying-with-docker
RUN turbo prune --scope=@examples/nextjs-app --docker --out-dir=./out/nextjs-app/
RUN turbo prune --scope=@examples/nextjs-app --docker --out-dir=./.turbo-pruned/nextjs-app/

#############################################################
# Stage 2 - App installation #
Expand Down Expand Up @@ -57,8 +57,8 @@ WORKDIR /app

# First install the dependencies (as they change less often)
COPY --link .gitignore ./
COPY --from=prepare --link /app/out/nextjs-app/json/ .
COPY --from=prepare --link /app/out/nextjs-app/yarn.lock ./yarn.lock
COPY --from=prepare --link /app/.turbo-pruned/nextjs-app/json/ .
COPY --from=prepare --link /app/.turbo-pruned/nextjs-app/yarn.lock ./yarn.lock

# Option 1: run install without cache
#RUN yarn install --inline-builds
Expand All @@ -69,7 +69,7 @@ RUN --mount=type=cache,target=/root/.yarn3-cache,id=yarn3-cache,sharing=locked \
yarn install --inline-builds

# Build the project
COPY --link --from=prepare /app/out/nextjs-app/full/ .
COPY --link --from=prepare /app/.turbo-pruned/nextjs-app/full/ .
COPY --link .gitignore turbo.jsonc tsconfig.base.json ./

ENV NEXT_BUILD_IGNORE_ESLINT=true
Expand Down Expand Up @@ -101,20 +101,19 @@ ENV NODE_ENV=production

RUN apt-get update \
&& apt-get install bash tzdata --no-install-recommends -y \
&& rm -rf /var/cache/apt/* \
&& rm -rf /var/cache/apt/*
# If needed, we can enable corepack in the runner as well, but for now we don't need it \
# since we're running the built server.js directly with node.
&& npm i -g corepack@${COREPACK_VERSION} && corepack enable
# && npm i -g corepack@${COREPACK_VERSION} && corepack enable


WORKDIR /app

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN addgroup --system --gid 1001 nodejs \
adduser --system --uid 1001 nextjs \
mkdir .next \
chown nextjs:nodejs .next
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0 Missing && operators — commands won't run

The line-continuation backslashes \ here do NOT separate shell commands; they simply continue a single shell command. As written, adduser, mkdir, and chown are passed as extra positional arguments to addgroup, which will either reject them or silently ignore them. As a result, the nextjs user is never created, the .next directory is never created/owned, and USER nextjs on the next line will cause the container to fail to start (or run as an unintended user on some runtimes).

Suggested change
RUN addgroup --system --gid 1001 nodejs \
adduser --system --uid 1001 nextjs \
mkdir .next \
chown nextjs:nodejs .next
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs \
&& mkdir .next \
&& chown nextjs:nodejs .next


USER nextjs

Expand Down
132 changes: 132 additions & 0 deletions examples/apps/nextjs-app/docker/Dockerfile.distroless
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
ARG NODE_VERSION=24.14
ARG DISTROLESS_IMAGE=gcr.io/distroless/nodejs24-debian13
ARG DEBIAN_VERSION=trixie-slim
ARG COREPACK_VERSION=0.34.6


#############################################################
# Stage 1 - App extraction / pruning #
#############################################################

FROM node:${NODE_VERSION}-${DEBIAN_VERSION} AS prepare

RUN apt-get update \
#&& apt-get install build-essential cmake curl unzip ca-certificates git jq --no-install-recommends -y \
&& apt-get install git jq --no-install-recommends -y \
&& rm -rf /var/cache/apt/* \
## Corepack won't be bundled in node 25+
&& npm i -g corepack@${COREPACK_VERSION} && corepack enable


WORKDIR /app

COPY --link package.json turbo.jsonc ./

# We can't run turbo without yarn install first, let's install locally and make sure
# both local and docker are aligned on the package.json version.
RUN TURBO_VERSION=$(cat package.json | jq '.devDependencies["turbo"]' -r) npm i -g turbo@${TURBO_VERSION}

COPY --link . .

# https://turbo.build/repo/docs/handbook/deploying-with-docker
RUN turbo prune --scope=@examples/nextjs-app --docker --out-dir=./.turbo-pruned/nextjs-app/

#############################################################
# Stage 2 - App installation #
#############################################################

FROM prepare AS builder

ENV TZ=Etc/UTC

# Optimize for YARN installation speed
ENV YARN_ENABLE_GLOBAL_CACHE=false
ENV YARN_ENABLE_MIRROR=false
ENV YARN_ENABLE_TELEMETRY=false
ENV YARN_NODE_LINKER=node-modules
ENV YARN_NM_MODE=hardlinks-local
ENV YARN_ENABLE_HARDENED_MODE=0
ENV YARN_ENABLE_CONSTRAINTS_CHECKS=false
# If using different compression level than in local (recommended: prefer to not do this)
#ENV YARN_COMPRESSION_LEVEL 0
#ENV YARN_CHECKSUM_BEHAVIOR ignore

# Disabling some well-known postinstall scripts
ENV PRISMA_SKIP_POSTINSTALL_GENERATE=true
ENV HUSKY=0

WORKDIR /app

# First install the dependencies (as they change less often)
COPY --link .gitignore ./
COPY --from=prepare --link /app/.turbo-pruned/nextjs-app/json/ .
COPY --from=prepare --link /app/.turbo-pruned/nextjs-app/yarn.lock ./yarn.lock

# Option 1: run install without cache
#RUN yarn install --inline-builds

# Option 2: run install with buildx cache mount (buildx)
RUN --mount=type=cache,target=/root/.yarn3-cache,id=yarn3-cache,sharing=locked \
YARN_CACHE_FOLDER=/root/.yarn3-cache \
yarn install --inline-builds

# Build the project
COPY --link --from=prepare /app/.turbo-pruned/nextjs-app/full/ .
COPY --link .gitignore turbo.jsonc tsconfig.base.json ./

ENV NEXT_BUILD_IGNORE_ESLINT=true
ENV NEXT_BUILD_IGNORE_TYPECHECK=true
ENV NEXT_BUILD_OUTPUT=standalone
ENV NODE_ENV=production
# ENV NEXT_BUILD_ENV_SENTRY_ENABLED=false
# ENV NEXT_BUILD_ENV_SENTRY_TRACING=false

RUN yarn workspace @examples/db-sqlserver prisma-generate

RUN yarn turbo run build --filter=@examples/nextjs-app
# Alternative we can also
#RUN yarn turbo run build --filter=@examples/nextjs-app...

#############################################################
# Stage 3 - App runner #
#############################################################

FROM ${DISTROLESS_IMAGE} AS runner

ARG NEXTJS_APP_PORT

# Bort PORT / HOSTNAME envs are respected by nextjs start/dev.
ENV HOSTNAME=0.0.0.0
ENV PORT=${NEXTJS_APP_PORT:-3000}
ENV TZ=Etc/UTC
ENV NODE_ENV=production

#RUN apt-get update \
# && apt-get install tzdata --no-install-recommends -y \
# && rm -rf /var/cache/apt/*

WORKDIR /app

#RUN addgroup --system --gid 1001 nodejs
#RUN adduser --system --uid 1001 nextjs

# Set the correct permission for prerender cache
#RUN mkdir .next
#RUN chown nextjs:nodejs .next

#USER nextjs
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Distroless runner stage running as root

The addgroup/adduser/USER nextjs setup is entirely commented out in the distroless stage, meaning the container runs as root (UID 0) in production. The gcr.io/distroless/nodejs* images ship a built-in nonroot user (UID 65532). Running as root is unnecessary here and contradicts the security principle of least privilege.

Consider switching to the non-root user that the distroless image already provides:

USER nonroot

or, equivalently, use the :nonroot image variant:

ARG DISTROLESS_IMAGE=gcr.io/distroless/nodejs24-debian13:nonroot


COPY --from=builder /app/examples/apps/nextjs-app/next.config.mjs \
/app/examples/apps/nextjs-app/package.json \
./

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

EXPOSE ${PORT}

CMD ["node", "examples/apps/nextjs-app/server.js"]

17 changes: 17 additions & 0 deletions examples/apps/nextjs-app/docker/docker-compose.distroless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: flowblade-example-nextjs
services:
app-distroless:
build:
# Start from root of the monorepo
context: ../../../../
dockerfile: ./examples/apps/nextjs-app/docker/Dockerfile.distroless
restart: no
networks:
- flowblade-net
ports:
- 3000:3000

networks:
flowblade-net:
driver: bridge
enable_ipv6: false
37 changes: 0 additions & 37 deletions examples/apps/nextjs-app/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,43 +62,6 @@ let nextConfig = {
},
},

/*
turbopack: {
rules: {
'*.wasm': {
loaders: [
{
loader: 'file-loader',
options: {
esModule: true,
},
},
],
as: '*.js',
},
},
}, */

/*
webpack: (config, { isServer }) => {
config.module.rules.push({
test: /\.wasm/,
loader: 'file-loader',
options: {},
});

config.experiments = {
...config.experiments,
asyncWebAssembly: true, // Enable async WebAssembly support
};

// config.module.rules.push({
// test: /\.wasm$/,
// type: 'asset/resource', // Treat .wasm files as assets
// });
return config;
}, */

async headers() {
return [
{
Expand Down
Loading