Dockerizing Angular Apps with Nginx
Step-by-step guide to building a multi-stage Docker image for Angular applications, served by Nginx with gzip compression and proper SPA routing.
title: Dockerizing Angular Apps with Nginx slug: dockerize-angular-nginx summary: Step-by-step guide to building a multi-stage Docker image for Angular applications, served by Nginx with gzip compression and proper SPA routing. date: 2024-04-20 author: Jordan Kim category: devops tags: [docker, angular, performance] status: published featured: false#
Dockerizing Angular Apps with Nginx#
Deploying an Angular application requires more than just running ng build. You need a production-grade web server to serve static files, handle gzip compression, and route all URLs back to index.html for proper SPA navigation. Nginx is the industry standard for this, and Docker makes it reproducible across environments.
Multi-Stage Dockerfile#
A multi-stage build keeps your final image small by separating the build environment from the runtime environment.
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Copy dependency files first for layer caching
COPY package*.json ./
RUN npm ci
# Copy source and build
COPY . .
RUN npm run build -- --configuration production
# Stage 2: Runtime
FROM nginx:alpine
# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built assets from builder stage
COPY --from=builder /app/dist/my-app/browser /usr/share/nginx/html
# Expose port 80
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
The first stage uses the full Node.js image to install dependencies and compile the application. The second stage starts from the lightweight Nginx Alpine image and copies only the compiled static files — the final image contains no build tools, keeping it under 50 MB.
Nginx Configuration for SPA Routing#
Angular applications use client-side routing. Without server-side configuration, refreshing a page at /articles/getting-started returns a 404 because Nginx looks for a physical directory that does not exist.
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# All routes fallback to index.html
location / {
try_files $uri $uri/ /index.html;
}
}
The try_files directive is the key line: it attempts to serve the requested URI as a file, then as a directory, and finally falls back to index.html where Angular's router takes over.
Docker Compose for Local Development#
A docker-compose.yml file simplifies local testing of your production image.
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- '8080:80'
restart: unless-stopped
Run with docker-compose up --build. The application will be available at http://localhost:8080.
Optimizing Build Performance#
For faster CI builds, leverage Docker layer caching by copying package*.json and running npm ci before copying the rest of your source code. Dependencies change less frequently than source files, so this layer is cached across builds.
If your CI platform supports BuildKit, enable it for faster parallel layer builds:
DOCKER_BUILDKIT=1 docker build -t my-angular-app .
Environment-Specific Configuration#
Angular applications often need different API endpoints or feature flags per environment. Since the Docker image contains pre-built static files, you cannot rely on Angular's environment.ts replacement at runtime.
One solution is to generate a config.json file at container startup using an entrypoint script:
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
cat > /usr/share/nginx/html/assets/config.json <<EOF
{
"apiUrl": "${API_URL}",
"featureFlag": "${FEATURE_FLAG}"
}
EOF
exec nginx -g 'daemon off;'
Your Angular app fetches assets/config.json on bootstrap to read runtime configuration.
Security Headers#
Add security headers to your Nginx configuration for production deployments:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
These headers protect against clickjacking, MIME-type sniffing, and referrer leakage.
A properly Dockerized Angular application with Nginx is fast, cache-friendly, and handles SPA routing correctly. The multi-stage build ensures a minimal attack surface and fast deployments.