```dockerfile # Stage 1: Build application and install production dependencies FROM node:20-alpine AS builder
WORKDIR /app
# Copy package.json and package-lock.json first to cache npm ci COPY package*.json ./ RUN npm ci --omit=dev --no-progress
# Copy application source code COPY . .
# Stage 2: Create the lean production image FROM alpine:3.18
# Create a non-root user and group ARG UID=1000 ARG GID=1000 RUN addgroup -g ${GID} nodeuser && adduser -u ${UID} -G nodeuser -s /bin/sh -D nodeuser
WORKDIR /app
# Copy production dependencies and application code from the builder stage COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app ./
# Set appropriate permissions for the application directory RUN chown -R nodeuser:nodeuser /app
# Run as the non-root user USER nodeuser
EXPOSE 3000
# Command to run the application CMD ["node", "src/index.js"] ```
``yaml version: '3.8' services: my-node-api: build: context: . dockerfile: Dockerfile ports: - "3000:3000" volumes: - .:/app - /app/node_modules # Prevents host node_modules from overriding container's environment: NODE_ENV: development PORT: 3000 restart: unless-stopped ``
Build and Runtime Notes
Build Specifics: The Dockerfile uses a multi-stage build. The initial builder stage, based on node:20-alpine, handles dependency installation. npm ci --omit=dev ensures only production dependencies are installed, keeping the final image lean by excluding development tools and test frameworks. Application source code is copied into this stage for preparation.
Runtime Considerations: The production image is built from alpine:3.18. This minimal base image significantly reduces the potential attack surface. A dedicated non-root user, nodeuser, is created and assigned to run the application, adhering to the principle of least privilege. Only the essential production node_modules and compiled application code are transferred from the builder stage. The application is configured to listen on port 3000.
Security Best Practices: Running the container with a non-root user (nodeuser) is a foundational security measure, mitigating the impact of any potential compromise. The choice of an alpine base image minimizes the number of included packages, reducing the overall attack surface and potential vulnerabilities. Regular security scanning of the final image is advised. Avoid embedding sensitive data directly; use environment variables or a dedicated secrets management system.
Image Size Targets: The multi-stage build is key to achieving an image size under 150MB. By separating the build environment from the runtime, development dependencies and build tools are discarded. npm ci --omit=dev further prunes unnecessary packages. The inherently lightweight alpine base image, combined with these practices, ensures the final image contains only the absolute necessities for application execution, maintaining a small and efficient footprint.