Skip to main content
Angular Deployment

Deployment Overview

Estimated Time: 3 hours | Difficulty: Intermediate | Prerequisites: Build process, Git
Learn how to deploy Angular applications to various platforms with automated CI/CD pipelines, environment management, and production optimizations.
┌─────────────────────────────────────────────────────────────────────────┐
│                    Deployment Pipeline                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐             │
│   │  Code   │───►│  Build  │───►│  Test   │───►│ Deploy  │             │
│   │  Push   │    │         │    │         │    │         │             │
│   └─────────┘    └─────────┘    └─────────┘    └─────────┘             │
│        │              │              │              │                    │
│        ▼              ▼              ▼              ▼                    │
│   ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐             │
│   │ Trigger │    │ Install │    │  Unit   │    │ Preview │             │
│   │   CI    │    │  Deps   │    │  Tests  │    │  Stage  │             │
│   │         │    │  Lint   │    │   E2E   │    │  Prod   │             │
│   └─────────┘    └─────────┘    └─────────┘    └─────────┘             │
│                                                                          │
│   Environments:                                                          │
│   • Development → localhost:4200                                        │
│   • Preview → pr-123.preview.example.com                                │
│   • Staging → staging.example.com                                        │
│   • Production → www.example.com                                         │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Build Optimization

Production Build

# Standard production build
ng build --configuration production

# With additional optimizations
ng build --configuration production \
  --output-hashing=all \
  --source-map=false

# Analyze bundle size
ng build --configuration production --stats-json
npx webpack-bundle-analyzer dist/my-app/stats.json

Environment Configuration

// environments/environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
  features: {
    analytics: false,
    newDashboard: true
  }
};

// environments/environment.production.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.example.com',
  features: {
    analytics: true,
    newDashboard: true
  }
};

// environments/environment.staging.ts
export const environment = {
  production: true,
  apiUrl: 'https://staging-api.example.com',
  features: {
    analytics: true,
    newDashboard: true
  }
};
// angular.json
{
  "configurations": {
    "production": {
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "500kb",
          "maximumError": "1mb"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "4kb",
          "maximumError": "8kb"
        }
      ],
      "outputHashing": "all",
      "optimization": true,
      "sourceMap": false,
      "namedChunks": false,
      "extractLicenses": true,
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.production.ts"
        }
      ]
    },
    "staging": {
      "budgets": [
        { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }
      ],
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.staging.ts"
        }
      ],
      "optimization": true,
      "sourceMap": true
    }
  }
}

GitHub Actions

Complete CI/CD Pipeline

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20.x'
  ANGULAR_CLI_VERSION: '17'

jobs:
  # Job 1: Lint and Test
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Lint
        run: npm run lint
      
      - name: Run unit tests
        run: npm run test:ci
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
  
  # Job 2: Build
  build:
    runs-on: ubuntu-latest
    needs: test
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build:prod
        env:
          CI: true
      
      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7
  
  # Job 3: E2E Tests
  e2e:
    runs-on: ubuntu-latest
    needs: build
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      
      - name: Run E2E tests
        uses: cypress-io/github-action@v6
        with:
          start: npx http-server dist/my-app/browser -p 4200
          wait-on: 'http://localhost:4200'
          browser: chrome
      
      - name: Upload E2E screenshots
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: cypress-screenshots
          path: cypress/screenshots
  
  # Job 4: Deploy Preview (PRs)
  deploy-preview:
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'pull_request'
    
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      
      - name: Deploy to Vercel Preview
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          working-directory: dist/my-app/browser
      
      - name: Comment PR with preview URL
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '🚀 Preview deployed to: ${{ steps.deploy.outputs.preview-url }}'
            })
  
  # Job 5: Deploy Staging
  deploy-staging:
    runs-on: ubuntu-latest
    needs: [build, e2e]
    if: github.ref == 'refs/heads/develop'
    environment: staging
    
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      
      - name: Deploy to AWS S3
        uses: jakejarvis/s3-sync-action@master
        with:
          args: --delete
        env:
          AWS_S3_BUCKET: ${{ secrets.STAGING_S3_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          SOURCE_DIR: 'dist/my-app/browser'
      
      - name: Invalidate CloudFront
        uses: chetan/invalidate-cloudfront-action@v2
        env:
          DISTRIBUTION: ${{ secrets.STAGING_CF_DISTRIBUTION }}
          PATHS: '/*'
          AWS_REGION: 'us-east-1'
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  
  # Job 6: Deploy Production
  deploy-production:
    runs-on: ubuntu-latest
    needs: [build, e2e]
    if: github.ref == 'refs/heads/main'
    environment: production
    
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      
      - name: Deploy to AWS S3
        uses: jakejarvis/s3-sync-action@master
        with:
          args: --delete
        env:
          AWS_S3_BUCKET: ${{ secrets.PROD_S3_BUCKET }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          SOURCE_DIR: 'dist/my-app/browser'
      
      - name: Invalidate CloudFront
        uses: chetan/invalidate-cloudfront-action@v2
        env:
          DISTRIBUTION: ${{ secrets.PROD_CF_DISTRIBUTION }}
          PATHS: '/*'
          AWS_REGION: 'us-east-1'
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      
      - name: Notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Production deployment completed!'
          fields: repo,message,commit,author
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Package.json Scripts

{
  "scripts": {
    "start": "ng serve",
    "build": "ng build",
    "build:prod": "ng build --configuration production",
    "build:staging": "ng build --configuration staging",
    "test": "ng test",
    "test:ci": "ng test --no-watch --no-progress --browsers=ChromeHeadless --code-coverage",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "analyze": "ng build --configuration production --stats-json && npx webpack-bundle-analyzer dist/my-app/stats.json"
  }
}

Docker Deployment

Dockerfile

# Build stage
FROM node:20-alpine AS build
WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci

# Copy source and build
COPY . .
RUN npm run build:prod

# Production stage
FROM nginx:alpine AS production
WORKDIR /usr/share/nginx/html

# Remove default nginx website
RUN rm -rf ./*

# Copy built assets
COPY --from=build /app/dist/my-app/browser .

# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Add healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:80/health || exit 1

EXPOSE 80

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

Nginx Configuration

# nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com;" always;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml;
    
    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Don't cache index.html
    location = /index.html {
        expires -1;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }
    
    # Angular routing - try file, then directory, then fallback to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # Health check endpoint
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
    
    # API proxy (if needed)
    location /api/ {
        proxy_pass http://api-server:3000/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - app-network
    depends_on:
      - api
  
  api:
    image: my-api:latest
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
    networks:
      - app-network
    depends_on:
      - db
  
  db:
    image: postgres:15-alpine
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=myapp
    networks:
      - app-network

volumes:
  postgres-data:

networks:
  app-network:
    driver: bridge

Cloud Platform Deployments

Vercel

// vercel.json
{
  "buildCommand": "npm run build:prod",
  "outputDirectory": "dist/my-app/browser",
  "framework": null,
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ]
}

Netlify

# netlify.toml
[build]
  command = "npm run build:prod"
  publish = "dist/my-app/browser"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    X-Content-Type-Options = "nosniff"

Firebase Hosting

// firebase.json
{
  "hosting": {
    "public": "dist/my-app/browser",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [
      {
        "source": "**/*.@(js|css)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=31536000"
          }
        ]
      }
    ]
  }
}
# Deploy to Firebase
npm install -g firebase-tools
firebase login
firebase init hosting
npm run build:prod
firebase deploy

Monitoring & Logging

// error-handler.service.ts
import * as Sentry from '@sentry/angular';

@Injectable({ providedIn: 'root' })
export class ErrorHandlerService implements ErrorHandler {
  handleError(error: Error): void {
    console.error('Application error:', error);
    
    // Send to Sentry
    Sentry.captureException(error);
    
    // Log to backend
    this.logError(error);
  }
  
  private http = inject(HttpClient);
  
  private logError(error: Error): void {
    const errorLog = {
      message: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString(),
      url: window.location.href,
      userAgent: navigator.userAgent
    };
    
    this.http.post('/api/logs/error', errorLog).subscribe();
  }
}

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    { provide: ErrorHandler, useClass: ErrorHandlerService },
    Sentry.createErrorHandler({ showDialog: false })
  ]
};

Deployment Checklist

┌─────────────────────────────────────────────────────────────────────────┐
│              Pre-Deployment Checklist                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   Build & Tests:                                                         │
│   □ All tests passing                                                    │
│   □ No linting errors                                                    │
│   □ Build succeeds without errors                                        │
│   □ Bundle size within budget                                            │
│                                                                          │
│   Configuration:                                                         │
│   □ Environment variables set                                            │
│   □ API URLs configured for production                                   │
│   □ Feature flags updated                                                │
│   □ Analytics/tracking enabled                                           │
│                                                                          │
│   Security:                                                              │
│   □ HTTPS enabled                                                        │
│   □ Security headers configured                                          │
│   □ CSP policy in place                                                  │
│   □ Sensitive data removed from bundles                                  │
│                                                                          │
│   Performance:                                                           │
│   □ Source maps disabled (or uploaded to error tracking)                │
│   □ Assets optimized and compressed                                      │
│   □ Caching headers configured                                           │
│   □ CDN configured                                                       │
│                                                                          │
│   Monitoring:                                                            │
│   □ Error tracking enabled                                               │
│   □ Performance monitoring enabled                                       │
│   □ Health checks configured                                             │
│   □ Alerts set up                                                        │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Next: Micro-frontends

Build scalable applications with micro-frontend architecture