Ship internal tools in hours, not weeks. Real auth, users, jobs, audit logs, and cohesive UI included. Early access $249 $499 → [Get it now]

How to containerize Angular app

Containerizing an Angular application is a standard practice for modern web development, ensuring consistency across development, staging, and production environments. With over 25 years of experience in software development and as the creator of CoreUI, I have architected and deployed hundreds of containerized frontend applications. The most efficient way to achieve this is through a multi-stage Docker build, which separates the build environment from the production runtime. This approach results in lightweight, secure, and high-performance images ready for any cloud provider, and it is exactly how we handle deployments for our Angular Dashboard Template.

Use a multi-stage Dockerfile to build the Angular application with Node.js and serve the static files using a lightweight Nginx server.

Step 1: Create a Multi-Stage Dockerfile

The first step is to define the build process. We use a “build” stage to compile the Angular code and a “runtime” stage to serve it. This keeps the final image size small because it doesn’t include Node.js or the node_modules folder.

# Stage 1: Build the Angular application
# Use a specific Node.js version for consistency
FROM node:20-alpine AS build

# Set the working directory inside the container
WORKDIR /app

# Copy package files first to leverage Docker cache
COPY package.json package-lock.json ./

# Install dependencies strictly
RUN npm ci

# Copy the rest of the application source code
COPY . .

# Build the application for production
# The output will be in the /app/dist/ folder
RUN npm run build -- --configuration production

# Stage 2: Serve the application using Nginx
# Use the official lightweight Nginx alpine image
FROM nginx:stable-alpine

# Copy the custom nginx configuration
# We will create this file in the next step
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copy the build output from the first stage
# Angular 17+: output is in dist/your-app-name/browser
# Angular 16 and earlier: output is in dist/your-app-name (no /browser subfolder)
COPY --from=build /app/dist/your-app-name/browser /usr/share/nginx/html

# Expose port 80 to the outside world
EXPOSE 80

# Start Nginx in the foreground
CMD ["nginx", "-g", "daemon off;"]

In this code section, the FROM node:20-alpine AS build line starts the compilation phase. By copying package.json separately, we ensure that npm ci only runs when dependencies change, significantly speeding up subsequent builds. The final stage copies only the compiled assets into an Nginx image, resulting in a production-ready container that is typically under 25MB.

Step 2: Configure Nginx for Angular Routing

Angular is a Single Page Application (SPA), which means the browser handles routing. If a user refreshes the page on a sub-route (e.g., /dashboard), Nginx will try to find a file at that path and fail with a 404. We must configure Nginx to redirect all requests to index.html. Save this as nginx.conf in the root of your project (alongside the Dockerfile).

server {
    listen 80;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;

    # Handle compression for better performance
    gzip on;
    gzip_min_length 1000;
    gzip_proxied any;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    location / {
        # This is the magic line for SPAs
        # It attempts to serve the file, then the directory,
        # and finally falls back to index.html
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets for a long time
    location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ {
        expires 6M;
        access_log off;
        add_header Cache-Control 'public';
    }

    # Error handling
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

This configuration ensures that your Angular application behaves correctly when deep-linked or refreshed. The try_files directive is critical for modern frameworks. Additionally, I’ve included Gzip compression and cache headers, which are the same optimizations we implement in CoreUI components to ensure lightning-fast load times.

Step 3: Optimize with .dockerignore

To prevent unnecessary files (like node_modules or local logs) from being sent to the Docker daemon during the build, you should create a .dockerignore file in your root directory.

# Ignore local dependencies
node_modules
dist
.angular

# Ignore git metadata
.git
.gitignore

# Ignore IDE settings
.vscode
.idea

# Ignore OS files
.DS_Store
Thumbs.db

# Ignore Docker files themselves
Dockerfile
docker-compose.yml
.dockerignore

# Ignore logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*

By excluding these files, you reduce the “build context” size, making the COPY . . command much faster. It also prevents local environment configurations from leaking into the container, which is vital for security and reproducibility.

Step 4: Build the Docker Image

Once your files are ready, you can build the image using the terminal. It is a good practice to tag your image with a version number or latest.

# Build the image from the current directory
# Ensure you are in the folder containing the Dockerfile
docker build -t my-angular-app:latest .

# Verify the image was created
docker images | grep my-angular-app

When you run this command, Docker executes every instruction in your Dockerfile. You will see the Node.js stage installing packages and compiling your TypeScript code into JavaScript, followed by the Nginx stage preparing the final image. This process ensures that if the build succeeds on your machine, it will succeed everywhere.

Step 5: Run the Container

After building the image, you can start a container instance. We map the container’s internal port 80 to a port on your host machine (like 8080).

# Run the container in detached mode (-d)
# Map host port 8080 to container port 80
docker run -d -p 8080:80 --name angular-container my-angular-app:latest

# Check if the container is running
docker ps

# To stop the container later
# docker stop angular-container

Once executed, your application will be available at http://localhost:8080. This encapsulated environment is perfect for testing production builds locally without needing to install specific versions of Node.js or Nginx on your host system.

Step 6: Using Docker Compose for Local Development

While a single Dockerfile is great for production, docker-compose simplifies managing multiple containers or setting up a consistent development environment for your team.

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '8080:80'
    restart: always
    container_name: angular-app-prod

By using this YAML file, you can start your entire stack with a single command: docker-compose up -d. This is especially useful when your Angular app needs to communicate with a backend API or a database container during local testing.

Explanation:

The process starts with a multi-stage build using Node.js to compile the Angular source code into highly optimized static files. We then transition to a second stage using Nginx, which acts as a specialized web server. By copying only the dist folder into the Nginx image, we eliminate the overhead of the build tools and source files. The custom nginx.conf is essential because it instructs the server to route all traffic back to index.html, allowing the Angular router to take over. This workflow ensures that your application is served efficiently with proper compression and caching strategies.

Best Practice Note:

Always use npm ci instead of npm install inside your Dockerfile to ensure reproducible builds based on your package-lock.json. This prevents “it works on my machine” issues caused by version drift. For large-scale enterprise applications, consider using our Angular Dashboard Template which comes with pre-configured build pipelines. To take containerization further, see how to deploy Angular with Docker for environment variables, secrets management, and multi-environment setups.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team