Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ on:
push:
branches: [main]
tags: ["v*.*.*"]
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
Expand Down
42 changes: 32 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,36 +1,58 @@
# syntax=docker/dockerfile:1

ARG NODE_IMAGE=node:22-bookworm-slim
ARG PNPM_VERSION=12.3.4

FROM ${NODE_IMAGE} AS base
# Toolchain for the stages that produce platform-independent output. Pinned to
# the build host's platform so that, in a multi-platform build, dependency
# installation and the Vite/Nitro build run once natively instead of once per
# target platform (the arm64 target would otherwise run them under QEMU).
# Note: --platform must be on the image reference; BuildKit ignores it when
# applied to a FROM that points at another stage.
FROM --platform=$BUILDPLATFORM ${NODE_IMAGE} AS build-base
ARG PNPM_VERSION
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
RUN corepack enable && corepack prepare pnpm@12.3.4 --activate
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate

# Install dependencies (including dev deps, needed to build) with pnpm's
# content-addressable store cached across builds.
FROM base AS deps
FROM build-base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
pnpm config set store-dir /pnpm/store \
&& pnpm install --frozen-lockfile

FROM base AS build
# Build the Nitro server output and derive the runtime manifest from the
# installed (lockfile-resolved) versions; see docker/write-runtime-package.mjs.
FROM build-base AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm run build
RUN pnpm run build && node docker/write-runtime-package.mjs

# Toolchain for the target platform, used to install the runtime node_modules
# for the architecture the image will actually run on.
FROM ${NODE_IMAGE} AS target-base
ARG PNPM_VERSION
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate

# The instrumentation and migration bootstrap run outside Nitro's bundle, so
# their imports must be resolvable from production node_modules.
FROM base AS prod-deps
# their imports must be resolvable from production node_modules. Nitro traces
# and inlines everything else into .output/server, so only the packages those
# entry points import directly are installed here, instead of the full
# production dependency tree. Lifecycle scripts are skipped because the only
# one in this tree (@sentry/cli's binary download) is build-time tooling.
FROM target-base AS prod-deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY --from=build /app/runtime-package.json ./package.json
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
pnpm config set store-dir /pnpm/store \
&& pnpm install --frozen-lockfile --prod
&& pnpm install --no-frozen-lockfile --ignore-scripts

# Final runtime image: the built Nitro server output plus production node_modules.
# Final runtime image: the built Nitro server output plus the minimal
# production node_modules above.
FROM ${NODE_IMAGE} AS runtime
ENV NODE_ENV=production
ENV HOST=0.0.0.0
Expand Down
24 changes: 24 additions & 0 deletions docker/write-runtime-package.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Writes a minimal package.json for the runtime image.
//
// Nitro traces and inlines the server's dependencies into .output/server, so
// the only packages that must exist in the runtime image's node_modules are
// the ones imported by the entry points that run outside the bundle:
// instrument.server.mjs and scripts/*.mjs. Versions are read from the
// installed node_modules (i.e. from pnpm-lock.yaml) so they cannot drift from
// the versions bundled into .output, which matters for Sentry in particular.
import { readFileSync, writeFileSync } from "node:fs";

const runtimePackages = ["@sentry/tanstackstart-react", "drizzle-orm", "pg"];

const dependencies = Object.fromEntries(
runtimePackages.map((name) => {
const { version } = JSON.parse(readFileSync(`node_modules/${name}/package.json`, "utf8"));
return [name, version];
}),
);

writeFileSync(
"runtime-package.json",
`${JSON.stringify({ name: "auth-runtime", private: true, type: "module", dependencies }, null, 2)}\n`,
);
console.info("runtime dependencies:", dependencies);
Loading