1- # Use official Node.js 20 image
1+ # ============== Stage 1: Build ==============
22FROM node:20-alpine AS builder
33
44WORKDIR /app
55
6- # Copy package files first
6+ # Install deps (devDependencies needed for TypeScript build)
77COPY package.json yarn.lock ./
88RUN yarn install --frozen-lockfile
99
10- # Copy source files and configs
1110COPY . .
12-
13- # Verify files exist
14- RUN ls -la && \
15- echo "Contents of tsconfig.json:" && \
16- cat tsconfig.json
17-
18- # Build the app
1911RUN yarn build
2012
21- # Production image
13+ # ============== Stage 2: Production (minimal, non-root) ==============
2214FROM node:20-alpine AS prod
15+
16+ # Non-root user (UID/GID 1001 to avoid conflict with node:20-alpine's default 1000)
17+ RUN addgroup -g 1001 appgroup && \
18+ adduser -u 1001 -G appgroup -s /bin/sh -D appuser
19+
2320WORKDIR /app
2421
25- # Copy only the necessary files
26- COPY --from=builder /app/package.json ./
27- COPY --from=builder /app/yarn.lock ./
28- COPY --from=builder /app/node_modules ./node_modules
22+ ENV NODE_ENV=production
23+ ENV PORT=5003
24+ # DATABASE_URL is not set in the image (secrets stay out of the build).
25+ # Pass it at runtime: docker run -e DATABASE_URL="postgresql://..." or use --env-file .env
26+
27+ # Production deps only; clean cache in same layer to avoid bloating image
28+ COPY package.json yarn.lock ./
29+ RUN yarn install --frozen-lockfile --production --ignore-optional && \
30+ yarn cache clean
31+
32+ # Copy built artifacts from builder
2933COPY --from=builder /app/dist ./dist
3034COPY --from=builder /app/migrations ./migrations
35+ COPY --from=builder /app/public ./public
36+
37+ # Own all app files as non-root user
38+ RUN chown -R appuser:appgroup /app
39+
40+ USER appuser
41+
42+ EXPOSE 5003
3143
32- EXPOSE 3000
33- CMD ["node" , "dist/app.js" ]
44+ CMD ["node" , "dist/app.js" ]
0 commit comments