← Retour au blog
DockerAngularProductionKubernetesContainer

Dockerizing an Angular app for production: optimized image, security, and reliable deployment

Docker has become essential for deploying Angular applications to production. Yet many developers settle for a basic Dockerfile that works locally but produces oversized images, security vulnerabilities, and unacceptable startup times in a cluster. The real challenge is building a lightweight, reproducible, and secure image that integrates smoothly into a robust CI/CD pipeline and orchestrators like Kubernetes. This process demands understanding multi-stage builds, layer optimization, base image selection, and environment variable management in production.

Multi-stage builds: separate compilation from runtime

The multi-stage build is the foundation of an optimized Angular image. The concept is straightforward: compile your app in a heavy image, then copy only the produced artifacts (the dist/ files) into a lightweight runtime image. Here's a concrete example: the first stage uses node:20-alpine for compilation, installs dependencies, runs ng build --configuration production, and generates a production-ready bundle. The second stage uses nginx:alpine as the base image, copies the compiled files into /usr/share/nginx/html, and configures Nginx to serve the SPA. This approach shrinks the final image size from 1.2 GB to roughly 50–80 MB, accelerating pulls in Kubernetes and reducing memory footprint.

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build -- --configuration production

FROM nginx:alpine
COPY --from=builder /app/dist/my-app /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Base image choice and security

Alpine Linux (node:20-alpine, nginx:alpine) is the standard for lightweight containers. At roughly 5 MB versus 150+ MB for Debian, it's an obvious choice. However, Alpine comes with a tradeoff: its C libraries are minimal (musl instead of glibc), which can create incompatibilities with certain native dependencies. Test your build locally using the Alpine image before deploying it. For security, never run containers as root in production: create a non-root user and assign minimal permissions. Nginx already runs under a dedicated nginx user by default, but ensure the /usr/share/nginx/html directory is readable without write access. Finally, scan your images with tools like Trivy or Grype to detect CVE vulnerabilities in dependencies, and keep your base images updated with security patches.

Nginx configuration for Angular

Nginx must be configured specifically to serve an Angular SPA. The common pitfall is forgetting route rewriting: without it, every non-existent URL returns a 404, when Angular should handle routing on the client side. Here's the essential configuration: all requests for static assets (assets, scripts, styles) are served directly, but requests to Angular routes are redirected to index.html. This allows Angular Router to take over and navigate correctly. Also add aggressive cache headers for hashed assets (immutable), and no-cache headers for index.html—otherwise users would remain on an old version after a deployment.

server {
  listen 80;
  root /usr/share/nginx/html;
  index index.html;

  location ~* \.(js|css|png|jpg|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  location / {
    try_files $uri $uri/ /index.html;
    add_header Cache-Control "no-cache, must-revalidate";
  }
}

Managing environment variables

In production, you cannot bake environment variables into the Docker build. A common approach is injecting variables at container startup via an initialization script. Create an assets/config.json file that gets populated dynamically, or use an entrypoint script that generates a configuration file before launching Nginx. With Kubernetes, you inject variables via ConfigMap or Secret, then mount them into the container. This lets you deploy the same image across dev, staging, and production with different configurations, without rebuilding each time. Never pass tokens or API keys as Docker environment variables visible in build history: use secrets managed by your orchestrator.

Optimizing image size and layers

Each line in a Dockerfile creates a layer that increases image size. Minimize layers by grouping RUN commands: RUN npm ci && npm run build instead of two separate lines. Use .dockerignore to exclude unnecessary files (node_modules, .git, README) before copying, which speeds up the build and reduces layer size. Clean npm caches after installation in production: RUN npm ci --only=production && npm cache clean --force . Finally, leverage Docker cache during development: copy package.json before the source code, so dependencies are cached and reused if only code changes.

Common pitfalls and monitoring

The most frequent pitfall is testing the image locally with docker run without simulating real production conditions (no memory limits, no orchestration, no secrets injected). Build and test the image exactly as it will be deployed: enforce memory limits, inject variables via ConfigMap, check logs. A second pitfall: forgetting to build in production mode or with an optimized build configuration. Verify that ng build --configuration production enables minification, ahead-of-time compilation (AOT), and dead code elimination. Finally, monitor image size and vulnerabilities: integrate Trivy into your CI/CD to automatically reject images containing critical CVEs. Also test startup times and memory consumption with tools like k6 or Locust in a staging environment.

Conclusion and next steps

Dockerizing an Angular app for production is not a matter of copying a Dockerfile from Stack Overflow. An optimized image rests on multi-stage builds, a secure Alpine base, strict Nginx configuration, and intelligent environment variable management. Start by setting up a robust Dockerfile with the best practices outlined here, integrate security scans into your CI/CD pipeline, and test under real conditions before production. Once in place, you'll have a lightweight, reproducible image ready for Kubernetes, capable of handling millions of requests with minimal memory footprint. The initial investment in solid Docker architecture pays dividends quickly in stability, deployment speed, and production confidence.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️