Skip to main content

Angular Docker Deployment

Introduction

Docker has revolutionized how applications are packaged and deployed by providing a consistent environment across development, testing, and production. For Angular applications, Docker offers significant advantages: it eliminates the "it works on my machine" problem, streamlines CI/CD pipelines, and makes scaling applications more manageable.

In this guide, we'll explore how to containerize an Angular application using Docker. We'll cover creating a Docker image, optimizing it for production, and deploying the containerized application.

Prerequisites

Before we begin, make sure you have:

  • Basic knowledge of Angular framework
  • Angular CLI installed (npm install -g @angular/cli)
  • Docker installed on your machine (Get Docker)
  • A working Angular application (or use a new one created with ng new my-app)

Docker Basics for Angular

What is Docker?

Docker is a platform that allows you to package applications and their dependencies into standardized units called containers. These containers are lightweight, isolated, and portable, making them perfect for deploying applications consistently across different environments.

Why Containerize Angular Applications?

  1. Environment Consistency: Same environment in development and production
  2. Isolation: Application dependencies are isolated from the host system
  3. Simplified Deployments: Streamlined deployments across different environments
  4. Scalability: Easily scale applications in container orchestration platforms like Kubernetes

Creating a Dockerfile for Angular

A Dockerfile contains instructions for building a Docker image. Let's create a basic Dockerfile for an Angular application:

dockerfile
# Stage 1: Build the Angular application
FROM node:16 as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build -- --configuration production

# Stage 2: Serve the application with Nginx
FROM nginx:1.21
COPY --from=build /app/dist/my-angular-app /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

This Dockerfile uses a multi-stage build process:

  1. Stage 1: Uses Node.js to build the Angular application
  2. Stage 2: Uses Nginx to serve the built application

Creating the Nginx Configuration

Create a file named nginx.conf in your project root:

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

location / {
try_files $uri $uri/ /index.html;
}
}

This configuration is essential for Angular's client-side routing to work correctly.

Building the Docker Image

To build the Docker image, navigate to your project directory and run:

bash
docker build -t my-angular-app .

This command builds a Docker image with the tag my-angular-app using the instructions in the Dockerfile.

Output:

Sending build context to Docker daemon  24.58MB
Step 1/10 : FROM node:16 as build
---> b9f398d30e45
Step 2/10 : WORKDIR /app
---> Using cache
---> a7b298c4a1a2
...
Successfully built 7f3b5e7a9e0a
Successfully tagged my-angular-app:latest

Running the Docker Container

After building the image, you can run it as a container:

bash
docker run -p 8080:80 my-angular-app

This command maps port 8080 on your host to port 80 in the container. You can access your Angular application by navigating to http://localhost:8080 in your browser.

Optimizing the Docker Image for Production

The basic Dockerfile works, but we can optimize it further for production:

dockerfile
# Stage 1: Build the Angular application
FROM node:16-alpine as build
WORKDIR /app

# Copy only package files first to leverage Docker cache
COPY package.json package-lock.json ./
RUN npm ci

# Copy the rest of the application and build it
COPY . .
RUN npm run build -- --configuration production

# Stage 2: Serve the application with Nginx
FROM nginx:1.21-alpine
COPY --from=build /app/dist/my-angular-app /usr/share/nginx/html

# Copy custom nginx config
COPY ./nginx.conf /etc/nginx/conf.d/default.conf

# Add health check
HEALTHCHECK --interval=30s --timeout=3s CMD wget -q --spider http://localhost/ || exit 1

# Expose port 80
EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Key optimizations:

  1. Using Alpine-based images to reduce size
  2. Leveraging Docker's caching mechanism with package files
  3. Using npm ci instead of npm install for more reliable builds
  4. Adding a health check to monitor container health

Docker Compose for Development

For development, you might want to use Docker Compose to manage your containers. Create a docker-compose.yml file:

yaml
version: '3'

services:
angular-app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "4200:4200"
volumes:
- ./src:/app/src
- ./package.json:/app/package.json
command: npm start

And a development Dockerfile (Dockerfile.dev):

dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
EXPOSE 4200
CMD ["npm", "start"]

Run it with:

bash
docker-compose up

This setup mounts your source code as a volume, allowing you to make changes and see them immediately without rebuilding the image.

Real-world Example: CI/CD Pipeline with Docker

Here's a practical example of how Docker fits into a CI/CD pipeline for an Angular application using GitHub Actions:

yaml
name: Build and Deploy

on:
push:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true


deploy:
needs: build
runs-on: ubuntu-latest

steps:
- name: Deploy to production
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull myregistry.com/my-angular-app:latest
docker stop my-angular-app || true
docker rm my-angular-app || true
docker run -d --name my-angular-app -p 80:80 myregistry.com/my-angular-app:latest

This workflow:

  1. Builds a Docker image of your Angular application
  2. Pushes it to a Docker registry
  3. Deploys it to a production server via SSH

Deploying to Cloud Platforms

Deploying to Azure App Service

bash
# Login to Azure
az login

# Create a resource group
az group create --name myResourceGroup --location eastus

# Create an App Service plan
az appservice plan create --name myAppServicePlan --resource-group myResourceGroup --sku B1 --is-linux

# Create a web app
az webapp create --resource-group myResourceGroup --plan myAppServicePlan --name my-angular-app --deployment-container-image-name myregistry.com/my-angular-app:latest

# Configure the web app to use the container
az webapp config container set --name my-angular-app --resource-group myResourceGroup --docker-custom-image-name myregistry.com/my-angular-app:latest

Deploying to AWS Elastic Container Service (ECS)

bash
# Create an ECS cluster
aws ecs create-cluster --cluster-name angular-cluster

# Register a task definition (requires JSON file)
aws ecs register-task-definition --cli-input-json file://task-definition.json

# Create a service
aws ecs create-service --cluster angular-cluster --service-name angular-service --task-definition angular-task:1 --desired-count 1

Common Issues and Troubleshooting

Issue: Angular Routes Not Working

Solution: Ensure your Nginx configuration has the proper redirect for client-side routing:

nginx
location / {
try_files $uri $uri/ /index.html;
}

Issue: Environment Variables in Angular

Solution: For Angular applications, environment variables need to be injected at build time or handled with a special approach at runtime:

dockerfile
# Build-time environment variable
RUN npm run build -- --configuration production --env.API_URL=https://api.example.com

For runtime environment variables, create a script to replace placeholders in your built files:

bash
#!/bin/sh
# env.sh
echo "Replacing environment variables..."
sed -i "s|API_URL_PLACEHOLDER|$API_URL|g" /usr/share/nginx/html/main.*.js
nginx -g "daemon off;"

Then update your Dockerfile:

dockerfile
COPY ./env.sh /usr/local/bin/env.sh
RUN chmod +x /usr/local/bin/env.sh
CMD ["/usr/local/bin/env.sh"]

Best Practices for Docker with Angular

  1. Use multi-stage builds to keep final images small
  2. Leverage Docker cache by organizing Dockerfile commands from least to most frequently changing
  3. Don't run containers as root for better security
  4. Include only production dependencies in your final image
  5. Use environment-specific configuration for different environments
  6. Implement health checks to monitor container health
  7. Use Docker Compose for local development to simplify setup

Summary

Docker provides a powerful way to package and deploy Angular applications consistently across different environments. By containerizing your Angular app:

  • You ensure that it runs the same way everywhere
  • You simplify deployment processes
  • You make scaling and managing your application easier

In this guide, we've covered:

  • Creating a basic Dockerfile for an Angular application
  • Optimizing the Docker image for production
  • Using Docker Compose for development
  • Implementing CI/CD pipelines with Docker
  • Deploying containerized Angular apps to cloud platforms
  • Troubleshooting common issues
  • Best practices for Docker with Angular

With these skills, you're now equipped to containerize and deploy Angular applications in a professional and efficient manner.

Additional Resources

Exercises

  1. Create a multi-stage Dockerfile for your Angular application and optimize it for production.
  2. Set up a Docker Compose configuration for local development with hot reloading.
  3. Implement a CI/CD pipeline using GitHub Actions that builds and pushes a Docker image.
  4. Configure environment-specific variables for your containerized Angular application.
  5. Deploy your containerized Angular application to a cloud provider of your choice.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)