Skip to content

Commit b876cd3

Browse files
2witstudiosclaude
andauthored
fix: Docker security hardening and CI version alignment (#712)
* fix: Docker security hardening and CI version alignment Remove .env COPY from realtime, migrate, and seed Dockerfiles — env vars are provided at runtime via docker-compose env_file. Add .env to .dockerignore. Convert realtime Dockerfile to multi-stage build with USER node. Enable --frozen-lockfile in processor build. Replace no-op worker healthcheck with pgrep process check. Enable NEXT_TELEMETRY_DISABLED in web Dockerfile. Align CI to Node 22 and Postgres 17 matching production. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address review feedback on realtime Dockerfile and worker healthcheck Realtime Dockerfile: - Add build steps for shared packages and realtime service (tsc) - Production-only install in runner stage (--prod) excludes devDependencies - Remove git/npm config (only pnpm is used, no git-based deps) - Runner now executes compiled JS via node instead of tsx Worker healthcheck: - Replace pgrep with node-based PID 1 liveness check - Avoids procps-ng dependency and ancestor shell match false-positives .dockerignore: - Use .env* glob with !.env.example override for broader coverage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add missing trailing newlines to worker and seed Dockerfiles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: narrow realtime Dockerfile COPY to only required packages and dist artifacts Builder stage: replace broad `COPY packages` with targeted copies of packages/db, packages/lib, and types (needed for build-time type roots). Runner stage: copy only compiled dist/ directories instead of full package trees — package.json files are already present from the prod install step, so Node module resolution via pnpm workspace links works correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2cb41b1 commit b876cd3

8 files changed

Lines changed: 50 additions & 43 deletions

File tree

.dockerignore

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ dist/
2828
!packages/**/dist/**
2929

3030
# Environment files
31-
.env.local
32-
.env.development.local
33-
.env.test.local
34-
.env.production.local
31+
.env*
32+
!.env.example
3533

3634
# IDE and editor files
3735
.vscode/

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
workflow_dispatch:
99

1010
env:
11-
NODE_VERSION: '20'
11+
NODE_VERSION: '22'
1212

1313
jobs:
1414
unit-tests:
@@ -17,7 +17,7 @@ jobs:
1717

1818
services:
1919
postgres:
20-
image: postgres:16
20+
image: postgres:17-alpine
2121
env:
2222
POSTGRES_PASSWORD: postgres
2323
POSTGRES_DB: pagespace_test

apps/processor/Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ COPY packages/db/package.json ./packages/db/
2727
COPY packages/lib/package.json ./packages/lib/
2828
COPY apps/processor/package.json ./apps/processor/
2929

30-
# Install all dependencies
31-
# Install dependencies (allow lockfile update since processor deps changed)
32-
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm config set store-dir /pnpm/store && pnpm install --no-frozen-lockfile --prod=false
30+
# Install dependencies
31+
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm config set store-dir /pnpm/store && pnpm install --frozen-lockfile --prod=false
3332

3433
# Now copy source code AFTER dependencies are installed
3534
COPY tsconfig.json ./

apps/realtime/Dockerfile

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,58 @@
11
# syntax=docker/dockerfile:1.6
2-
# This Dockerfile runs the realtime service.
3-
# It uses the same pattern as the working migrate container.
4-
FROM node:22.17.0-alpine
2+
# Multi-stage build for the realtime Socket.IO service
53

6-
# Set working directory
4+
# Stage 1: Install dependencies and build
5+
FROM node:22.17.0-alpine AS builder
76
WORKDIR /app
87

9-
# Install basic tools and configure npm
10-
RUN apk add --no-cache git && \
11-
npm config set fetch-timeout 300000 && \
12-
npm config set fetch-retry-maxtimeout 120000 && \
13-
npm config set fetch-retry-mintimeout 10000
14-
15-
# Enable corepack and prepare specific pnpm version
168
RUN corepack enable && \
179
(corepack prepare pnpm@10.13.1 --activate || corepack prepare pnpm@10.13.1 --activate || corepack prepare pnpm@10.13.1 --activate)
1810

19-
# Copy package files first for better Docker layer caching
2011
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
2112
COPY packages/db/package.json ./packages/db/
2213
COPY packages/lib/package.json ./packages/lib/
2314
COPY apps/web/package.json ./apps/web/
2415
COPY apps/realtime/package.json ./apps/realtime/
2516

26-
# Install ALL dependencies for the entire monorepo
2717
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm config set store-dir /pnpm/store && pnpm install --frozen-lockfile --prod=false
2818

29-
# Copy only the source code needed (avoid copying node_modules)
30-
COPY packages ./packages
31-
COPY apps ./apps
19+
COPY types ./types
20+
COPY packages/db ./packages/db
21+
COPY packages/lib ./packages/lib
22+
COPY apps/realtime ./apps/realtime
3223
COPY tsconfig.json ./
3324

34-
# Copy .env file for environment variables
35-
COPY .env ./
25+
# Build shared packages and the realtime service
26+
RUN pnpm --filter @pagespace/db build && \
27+
pnpm --filter @pagespace/lib build && \
28+
pnpm --filter realtime build
29+
30+
# Stage 2: Production runner
31+
FROM node:22.17.0-alpine AS runner
32+
WORKDIR /app
33+
34+
RUN corepack enable && \
35+
(corepack prepare pnpm@10.13.1 --activate || corepack prepare pnpm@10.13.1 --activate || corepack prepare pnpm@10.13.1 --activate)
36+
37+
# Copy manifests and lockfile for production install
38+
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
39+
COPY --from=builder /app/packages/db/package.json ./packages/db/
40+
COPY --from=builder /app/packages/lib/package.json ./packages/lib/
41+
COPY --from=builder /app/apps/web/package.json ./apps/web/
42+
COPY --from=builder /app/apps/realtime/package.json ./apps/realtime/
43+
44+
# Install production-only dependencies
45+
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm config set store-dir /pnpm/store && pnpm install --frozen-lockfile --prod
46+
47+
# Copy only compiled runtime artifacts from builder
48+
COPY --from=builder /app/packages/db/dist ./packages/db/dist
49+
COPY --from=builder /app/packages/lib/dist ./packages/lib/dist
50+
COPY --from=builder /app/apps/realtime/dist ./apps/realtime/dist
51+
52+
ENV NODE_ENV=production
53+
54+
USER node
3655

3756
EXPOSE 3001
3857

39-
# Run the realtime service with tsx
40-
CMD ["pnpm", "--filter", "realtime", "start"]
58+
CMD ["node", "apps/realtime/dist/index.js"]

apps/web/Dockerfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ ENV NEXT_PUBLIC_GOOGLE_OAUTH_IOS_CLIENT_ID=$NEXT_PUBLIC_GOOGLE_OAUTH_IOS_CLIENT_
4747

4848
# Next.js collects telemetry data by default.
4949
# Learn more here: https://nextjs.org/telemetry
50-
# Uncomment the following line in case you want to disable telemetry.
51-
# ENV NEXT_TELEMETRY_DISABLED 1
50+
ENV NEXT_TELEMETRY_DISABLED=1
5251

5352
# We are installing devDependencies via the --prod=false flag, so this is not needed
5453
# ENV NODE_ENV=development
@@ -65,8 +64,7 @@ FROM node:22.17.0-alpine AS runner
6564
WORKDIR /app
6665

6766
ENV NODE_ENV=production
68-
# Uncomment the following line in case you want to disable telemetry.
69-
# ENV NEXT_TELEMETRY_DISABLED 1
67+
ENV NEXT_TELEMETRY_DISABLED=1
7068

7169
# Use the existing node user (UID 1000) from the base image
7270
# This matches the processor container's UID for shared volume access

apps/web/Dockerfile.migrate

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,3 @@ COPY tsconfig.json ./
3434

3535
# Build the database package to ensure latest schema changes are compiled
3636
RUN pnpm --filter @pagespace/db build
37-
38-
# Copy .env file for tsx --env-file
39-
COPY .env ./

apps/web/Dockerfile.seed

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,5 @@ COPY packages ./packages
2626
COPY apps ./apps
2727
COPY tsconfig.json ./
2828

29-
# Copy .env file for tsx --env-file
30-
COPY .env ./
31-
3229
# Run seed
33-
CMD ["pnpm", "--filter", "@pagespace/db", "db:seed"]
30+
CMD ["pnpm", "--filter", "@pagespace/db", "db:seed"]

apps/web/Dockerfile.worker

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ COPY --from=deps --chown=worker:nodejs /app .
3939

4040
USER worker
4141

42-
# Health check
42+
# Health check - verify the main process (PID 1) is alive
4343
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
44-
CMD node -e "console.log('Worker health check')" || exit 1
44+
CMD node -e "try { process.kill(1, 0); } catch(e) { process.exit(1); }"
4545

4646
# Run the worker using tsx
47-
CMD ["pnpm", "tsx", "apps/web/src/workers/file-processor.ts"]
47+
CMD ["pnpm", "tsx", "apps/web/src/workers/file-processor.ts"]

0 commit comments

Comments
 (0)