Skip to Content
🚀 APSO is now in public beta. Get started →

Docker Deployment

Docker provides consistent, reproducible deployments for your APSO backend.

Dockerfile

TypeScript (NestJS)

# Build stage FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production stage FROM node:20-alpine AS production WORKDIR /app # Create non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nestjs -u 1001 COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/package*.json ./ USER nestjs EXPOSE 3001 ENV NODE_ENV=production ENV PORT=3001 CMD ["node", "dist/main.js"]

Python (FastAPI)

FROM python:3.11-slim WORKDIR /app # Install dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application COPY . . # Create non-root user RUN useradd -m appuser && chown -R appuser:appuser /app USER appuser EXPOSE 3001 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3001"]

Go (Gin)

# Build stage FROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server # Production stage FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /app COPY --from=builder /app/server . RUN adduser -D appuser USER appuser EXPOSE 3001 CMD ["./server"]

Docker Compose

Development

# docker-compose.yml version: '3.8' services: api: build: . ports: - "3001:3001" environment: - NODE_ENV=development - DATABASE_URL=postgresql://postgres:postgres@db:5432/apso - JWT_SECRET=dev-secret depends_on: db: condition: service_healthy volumes: - .:/app - /app/node_modules db: image: postgres:15-alpine ports: - "5432:5432" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: apso volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 volumes: postgres_data:

Production

# docker-compose.prod.yml version: '3.8' services: api: image: your-registry/apso-api:latest ports: - "3001:3001" environment: - NODE_ENV=production - DATABASE_URL=${DATABASE_URL} - JWT_SECRET=${JWT_SECRET} deploy: replicas: 3 restart_policy: condition: on-failure delay: 5s max_attempts: 3 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - api

.dockerignore

node_modules npm-debug.log Dockerfile* docker-compose* .git .gitignore .env* *.md .vscode coverage dist

Building Images

# Build image docker build -t my-api:latest . # Build with specific tag docker build -t my-api:v1.0.0 . # Build for different platforms docker buildx build --platform linux/amd64,linux/arm64 -t my-api:latest .

Running Containers

# Run with environment file docker run -p 3001:3001 --env-file .env.production my-api:latest # Run with specific environment variables docker run -p 3001:3001 \ -e DATABASE_URL="postgresql://..." \ -e JWT_SECRET="secret" \ my-api:latest # Run in background docker run -d --name my-api -p 3001:3001 --env-file .env.production my-api:latest # View logs docker logs -f my-api

Container Registry

Docker Hub

# Login docker login # Tag and push docker tag my-api:latest username/my-api:latest docker push username/my-api:latest

GitHub Container Registry

# Login echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin # Tag and push docker tag my-api:latest ghcr.io/username/my-api:latest docker push ghcr.io/username/my-api:latest

AWS ECR

# Login aws ecr get-login-password --region us-east-1 | \ docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com # Tag and push docker tag my-api:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest

Kubernetes

Deployment

# k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: apso-api spec: replicas: 3 selector: matchLabels: app: apso-api template: metadata: labels: app: apso-api spec: containers: - name: api image: your-registry/apso-api:latest ports: - containerPort: 3001 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: apso-secrets key: database-url - name: JWT_SECRET valueFrom: secretKeyRef: name: apso-secrets key: jwt-secret livenessProbe: httpGet: path: /health port: 3001 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 3001 initialDelaySeconds: 5 periodSeconds: 5 resources: requests: memory: "256Mi" cpu: "200m" limits: memory: "512Mi" cpu: "500m"

Service

# k8s/service.yaml apiVersion: v1 kind: Service metadata: name: apso-api spec: selector: app: apso-api ports: - port: 80 targetPort: 3001 type: ClusterIP

Ingress

# k8s/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: apso-api annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: - api.yourdomain.com secretName: apso-api-tls rules: - host: api.yourdomain.com http: paths: - path: / pathType: Prefix backend: service: name: apso-api port: number: 80

Health Checks

// Docker health check endpoint @Get('/health') health() { return { status: 'ok', timestamp: Date.now() }; } @Get('/health/ready') async ready() { // Check database connection await this.dataSource.query('SELECT 1'); return { status: 'ready' }; }
Last updated on