Files
ermnvldmr.com/docs/container-deployment.md
T

8.7 KiB

Container Deployment Guide

This guide covers the containerized deployment setup for the www.ermnvldmr.com website, including both the main Astro site and the Storybook component library.

Overview

The project uses Docker containers to package and deploy:

  • Main Site: Astro static website served via nginx
  • Storybook: Component library served via nginx
  • Registry: GitHub Container Registry (ghcr.io)
  • CI/CD: GitHub Actions with semantic versioning

Container Architecture

Multi-stage Build Strategy

Both containers follow a multi-stage build pattern:

  1. Builder Stage: Node.js 20 Alpine with pnpm for building assets
  2. Production Stage: nginx:1.25-alpine for serving static content

Security Features

  • Non-root user: Containers run as nginx-user (UID 1001)
  • Non-privileged ports: Expose port 8080 instead of 80
  • Security headers: CSP, HSTS, and other security headers configured
  • Signal handling: dumb-init for proper signal handling

Images

  • Main Site: ghcr.io/deytenit/www.ermnvldmr.com
  • Storybook: ghcr.io/deytenit/www.ermnvldmr.com/storybook

Tagging Strategy

Release Tags (from release/semantic-dispatch)

For both images:

  • vX.X.X - Full version (e.g., v1.2.3)
  • vX.X - Minor version (e.g., v1.2)
  • vX - Major version (e.g., v1)
  • latest - Latest stable release
  • {sha} - Commit SHA for traceability

Pre-release Tags (from next/semantic-dispatch)

For both images:

  • vX.X.X-next.X - Full prerelease version (e.g., v1.2.3-next.1)
  • vX.X.X-next - Latest prerelease for that version
  • vX.X-next - Latest prerelease for minor version
  • vX-next - Latest prerelease for major version
  • latest-next - Latest prerelease overall
  • {sha} - Commit SHA for traceability

Local Development

Using Docker Compose

Start both services locally:

# Start both containers
docker-compose up

# Start in background
docker-compose up -d

# View logs
docker-compose logs -f

# Stop services
docker-compose down

Access points:

Building Individual Images

# Build Astro site
docker build -f docker/astro/Dockerfile -t www-astro .

# Build Storybook
docker build -f docker/storybook/Dockerfile -t www-storybook .

# Run containers
docker run -p 3000:8080 www-astro
docker run -p 6006:8080 www-storybook

Production Deployment

Manual Container Deployment

Pull and run the latest images:

# Pull latest stable images
docker pull ghcr.io/deytenit/www.ermnvldmr.com:latest
docker pull ghcr.io/deytenit/www.ermnvldmr.com/storybook:latest

# Run main site
docker run -d \
  --name www-ermnvldmr-astro \
  -p 3000:8080 \
  --restart unless-stopped \
  ghcr.io/deytenit/www.ermnvldmr.com:latest

# Run storybook
docker run -d \
  --name www-ermnvldmr-storybook \
  -p 6006:8080 \
  --restart unless-stopped \
  ghcr.io/deytenit/www.ermnvldmr.com/storybook:latest

Environment Variables

The containers are designed to be stateless and don't require runtime environment variables. Configuration is baked into the build.

Health Checks

Both containers include health check endpoints:

# Check container health
curl http://localhost:3000/health  # Astro site
curl http://localhost:6006/health  # Storybook

Resource Requirements

Recommended minimums per container:

  • CPU: 0.1 cores
  • Memory: 64MB
  • Storage: 100MB

Production recommendations:

  • CPU: 0.25 cores
  • Memory: 128MB
  • Storage: 500MB (for logs and cache)

Reverse Proxy Configuration

nginx Configuration Example

server {
    listen 80;
    server_name www.ermnvldmr.com;
    
    location / {
        proxy_pass http://astro-container:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name storybook.ermnvldmr.com;
    
    location / {
        proxy_pass http://storybook-container:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Traefik Configuration Example

# docker-compose.yml with Traefik
version: '3.8'

services:
  astro:
    image: ghcr.io/deytenit/www.ermnvldmr.com:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.astro.rule=Host(`www.ermnvldmr.com`)"
      - "traefik.http.services.astro.loadbalancer.server.port=8080"

  storybook:
    image: ghcr.io/deytenit/www.ermnvldmr.com/storybook:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.storybook.rule=Host(`storybook.ermnvldmr.com`)"
      - "traefik.http.services.storybook.loadbalancer.server.port=8080"

Troubleshooting

Common Issues

Container Won't Start

# Check logs
docker logs container-name

# Common causes:
# - Missing dependencies in build
# - Permission issues
# - Port conflicts

Build Failures

# Check build logs
docker build -f docker/astro/Dockerfile -t test .

# Common causes:
# - Node version mismatch
# - pnpm cache issues
# - Missing build dependencies

Permission Denied

# Check if running as correct user
docker exec container-name whoami

# Should output: nginx-user

Debugging

Enable debug mode in nginx:

# Create debug container
docker run -it --entrypoint=/bin/sh ghcr.io/deytenit/www.ermnvldmr.com:latest

# Check nginx config
nginx -t

# Check file permissions
ls -la /usr/share/nginx/html/

Performance Tuning

nginx Optimization

For high-traffic deployments, consider:

  • Adjusting worker processes
  • Enabling gzip compression levels
  • Tuning keepalive settings
  • Adding rate limiting

Container Resources

Monitor and adjust:

# Check resource usage
docker stats

# Update container limits
docker run --memory=256m --cpus=0.5 ...

Security Considerations

Container Security

  • Containers run as non-root user
  • Minimal attack surface with Alpine base
  • Regular security updates via base image updates
  • No sensitive data in containers

Network Security

  • Expose only necessary ports
  • Use internal networks for service communication
  • Implement proper firewall rules
  • Enable HTTPS with reverse proxy

Registry Security

  • Images are signed and scanned
  • Access controlled via GitHub permissions
  • Automatic vulnerability scanning
  • Regular base image updates

Monitoring

Health Monitoring

# Simple health check script
#!/bin/bash
if curl -f http://localhost:3000/health >/dev/null 2>&1; then
    echo "Astro site: healthy"
else
    echo "Astro site: unhealthy"
    exit 1
fi

Log Management

# View logs
docker-compose logs -f astro
docker-compose logs -f storybook

# Log rotation (configure docker daemon)
# /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Metrics Collection

Consider integrating with:

  • Prometheus + Grafana
  • nginx prometheus exporter
  • Container metrics exporters
  • Application performance monitoring

CI/CD Integration

The containers are automatically built and pushed when:

  1. Pre-release: Merge to next branch triggers prerelease
  2. Release: Manual workflow dispatch creates stable release

Workflow Outputs

Each workflow provides:

  • new_release_published: Boolean indicating if release was created
  • new_release_version: The version number of the release

Manual Triggers

# Trigger release workflow via GitHub CLI
gh workflow run "release/semantic-dispatch" --ref main

# Trigger prerelease via merge to next branch
git checkout next
git merge feature-branch
git push origin next

Maintenance

Regular Updates

  1. Base Image Updates: Rebuild monthly for security patches
  2. Dependency Updates: Follow project dependency update schedule
  3. Configuration Review: Quarterly review of nginx and security settings

Backup Strategy

Since containers are stateless:

  • Source code is backed up in Git repository
  • Container images are versioned in registry
  • No persistent data to backup

Rollback Procedure

# Rollback to previous version
docker pull ghcr.io/deytenit/www.ermnvldmr.com:v1.2.2
docker stop current-container
docker run -d --name new-container ghcr.io/deytenit/www.ermnvldmr.com:v1.2.2

# Or use docker-compose with specific version
docker-compose down
# Edit docker-compose.yml to specify version
docker-compose up -d

For additional support or questions, refer to the project repository issues or documentation.