This guide covers deploying the Node.js API and Temporal Worker services using Docker and Kubernetes.
The application consists of two main services:
- API Service - Express.js REST API with OpenAPI documentation
- Worker Service - Temporal workflow workers that execute background tasks
Note: PostgreSQL database and Temporal Server are expected to be deployed separately (managed services or dedicated infrastructure).
- Docker 24.0+
- Docker Compose 3.8+ (for local development)
- Kubernetes 1.28+ (for production)
- kubectl configured with cluster access
- Container registry access (Docker Hub, GCR, ECR, etc.)
- External PostgreSQL database
- External Temporal Server (Temporal Cloud or self-hosted)
Create a .env file based on sample.env:
# Application
NODE_ENV=production
PORT=3015
LOG_LEVEL=info
# Database (external)
DB_HOST=your-postgres-host
DB_NAME=node_api_db
DB_USER=postgres
DB_PASSWORD=your-secure-password
DB_PORT=5432
# Temporal (external)
TEMPORAL_ADDRESS=temporal.yournamespace.tmprl.cloud:7233
# or for self-hosted: temporal-server:7233
# Other configs...# Build images
docker-compose build
# Start all services (API + Worker + Nginx)
docker-compose up -d
# View logs
docker-compose logs -f
# Stop services
docker-compose down# Run only API
docker-compose up -d api
# Run only Worker
docker-compose up -d worker
# Run with nginx reverse proxy
docker-compose up -d api worker nginx- API (direct): http://localhost:3015
- API (via nginx): http://localhost
- Swagger docs: http://localhost:3015/api-docs
- Health check: http://localhost:3015/health
# Set your container registry
REGISTRY=your-registry.com
VERSION=v1.0.0
# Build API image
docker build -f Dockerfile.api -t $REGISTRY/node-api:$VERSION --target production .
docker push $REGISTRY/node-api:$VERSION
# Build Worker image
docker build -f Dockerfile.worker -t $REGISTRY/node-api-worker:$VERSION --target production .
docker push $REGISTRY/node-api-worker:$VERSION# Create secret for sensitive data
kubectl create secret generic node-api-secrets \
--from-literal=DB_PASSWORD=your-db-password \
--from-literal=DB_HOST=your-postgres-host \
--from-literal=TEMPORAL_ADDRESS=temporal.yournamespace.tmprl.cloud:7233
# Verify secret
kubectl get secrets node-api-secretsEdit k8s-deployment.yaml:
- Replace
your-registry/node-api:latestwith your actual image - Replace
your-registry/node-api-worker:latestwith your actual image - Update
api.yourdomain.comwith your actual domain - Adjust resource limits based on your needs
- Update namespace if not using
default
# Create namespace (optional)
kubectl create namespace node-api
# Apply all manifests
kubectl apply -f k8s-deployment.yaml
# Check deployment status
kubectl get pods -l app=node-api
kubectl get services -l app=node-api
kubectl get ingress node-api-ingress# Check API pods
kubectl get pods -l component=api
kubectl logs -f deployment/node-api-service
# Check Worker pods
kubectl get pods -l component=worker
kubectl logs -f deployment/node-api-worker
# Check API service
kubectl get service node-api-service
# Check ingress
kubectl describe ingress node-api-ingressRun migrations as a Kubernetes Job:
# Create migration job
kubectl create job db-migration --image=$REGISTRY/node-api:$VERSION \
-- npm run migrate
# Check job status
kubectl get jobs
kubectl logs job/db-migrationThe nginx configuration handles:
- Rate limiting: 100 req/s per IP with burst of 20
- Health checks:
/healthendpoint for Kubernetes probes - SSL/TLS: TLS 1.2/1.3 with modern ciphers
- Security headers: HSTS, X-Frame-Options, X-XSS-Protection
- WebSocket support: For real-time features (if needed)
The nginx service automatically uses api:3015 as the upstream.
Update nginx configuration to use Kubernetes service DNS:
upstream api_backend {
server node-api-service.default.svc.cluster.local:3015;
}Or use Kubernetes Ingress (recommended) instead of nginx deployment.
API Service (scales with traffic):
# Manual scaling
kubectl scale deployment node-api-service --replicas=5
# Auto-scaling is configured via HPA (3-10 replicas)
kubectl get hpa node-api-hpaWorker Service (scales with workflow volume):
# Manual scaling
kubectl scale deployment node-api-worker --replicas=4
# Temporal workers can be scaled based on:
# - Number of workflows
# - Activity execution time
# - Resource utilizationUpdate resource limits in k8s-deployment.yaml:
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"# Local
curl http://localhost:3015/health
# Kubernetes
kubectl exec -it deployment/node-api-service -- wget -qO- http://localhost:3015/healthThe API service has three types of probes:
- Liveness Probe: Restarts pod if unhealthy
- Readiness Probe: Removes pod from service if not ready
- Startup Probe: Allows slow startup (up to 300s)
Workers don't have HTTP health checks. Temporal Server monitors worker health via:
- Heartbeat mechanism
- Task polling activity
- Worker registration status
Check worker status in Temporal Web UI or CLI:
tctl --namespace default task-queue describe user# Check pods
kubectl get pods -l component=api
kubectl describe pod <pod-name>
# Check logs
kubectl logs -f deployment/node-api-service
# Check service
kubectl get endpoints node-api-service# Check worker logs
kubectl logs -f deployment/node-api-worker
# Verify Temporal connection
kubectl exec -it deployment/node-api-worker -- env | grep TEMPORAL
# Check if workers are registered
tctl task-queue describe user# Test database connectivity from pod
kubectl exec -it deployment/node-api-service -- sh
apk add postgresql-client
psql -h $DB_HOST -U $DB_USER -d $DB_NAME# Check nginx logs
docker-compose logs nginx
# or
kubectl logs deployment/nginx
# Verify upstream is healthy
curl http://api:3015/health # from nginx container- Use non-root user: Dockerfiles run as user
nodejs (1001) - Secrets management: Use Kubernetes Secrets, never commit secrets
- Network policies: Restrict pod-to-pod communication
- Resource limits: Prevent resource exhaustion
- Image scanning: Scan images for vulnerabilities
- SSL/TLS: Always use HTTPS in production
- Rate limiting: Protect against DDoS attacks
Example GitHub Actions workflow:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and push API
run: |
docker build -f Dockerfile.api -t $REGISTRY/node-api:$VERSION .
docker push $REGISTRY/node-api:$VERSION
- name: Build and push Worker
run: |
docker build -f Dockerfile.worker -t $REGISTRY/node-api-worker:$VERSION .
docker push $REGISTRY/node-api-worker:$VERSION
- name: Deploy to Kubernetes
run: |
kubectl apply -f k8s-deployment.yaml
kubectl rollout status deployment/node-api-service# View rollout history
kubectl rollout history deployment/node-api-service
# Rollback to previous version
kubectl rollout undo deployment/node-api-service
# Rollback to specific revision
kubectl rollout undo deployment/node-api-service --to-revision=2- Environment variables configured
- Database migrations applied
- Secrets created in Kubernetes
- Images built and pushed to registry
- Resource limits configured appropriately
- SSL/TLS certificates configured
- DNS records pointing to ingress
- Monitoring and alerting set up
- Log aggregation configured
- Backup strategy for database
- Disaster recovery plan documented
- Load testing performed
For issues or questions:
- GitHub Issues: https://github.com/your-repo/issues
- Documentation: See README.md and API_DOCUMENTATION.md