- Deployment Automation
- When to use this skill
- New Projects
-
- Set up automated deployment from scratch
- Manual Deployment Improvement
-
- Automate repetitive manual tasks
- Multi-Environment
-
- Separate dev, staging, and production environments
- Scaling
- Introduce Kubernetes to handle traffic growth Instructions Step 1: Docker Containerization Package the application as a Docker image. Dockerfile (Node.js app):
Multi-stage build for smaller image size
FROM node:18-alpine AS builder WORKDIR /app
Copy package files and install dependencies
COPY package*.json ./ RUN npm ci --only=production
Copy source code
COPY . .
Build application (if needed)
RUN npm run build
Production stage
FROM node:18-alpine WORKDIR /app
Copy only necessary files from builder
COPY --from = builder /app/node_modules ./node_modules COPY --from = builder /app/dist ./dist COPY --from = builder /app/package.json ./
Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 USER nodejs
Expose port
EXPOSE 3000
Health check
HEALTHCHECK --interval = 30s --timeout = 3s --start-period = 5s --retries = 3 \ CMD node healthcheck.js
Start application
CMD [ "node" , "dist/index.js" ] .dockerignore : node_modules npm-debug.log .git .env .env.local dist build coverage .DS_Store Build and Run :
Build image
docker build -t myapp:latest .
Run container
docker run -d -p 3000 :3000 --name myapp-container myapp:latest
Check logs
docker logs myapp-container
Stop and remove
docker stop myapp-container docker rm myapp-container Step 2: GitHub Actions CI/CD Automatically runs tests and deploys on code push. .github/workflows/deploy.yml : name : CI/CD Pipeline on : push : branches : [ main , develop ] pull_request : branches : [ main ] env : NODE_VERSION : '18' REGISTRY : ghcr.io IMAGE_NAME : $ { { github.repository } } jobs : 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 : Run linter run : npm run lint - name : Run tests run : npm test - - - - coverage - name : Upload coverage uses : codecov/codecov - action@v3 with : files : ./coverage/coverage - final.json build : needs : test runs-on : ubuntu - latest if : github.event_name == 'push' && github.ref == 'refs/heads/main' steps : - uses : actions/checkout@v4 - name : Set up Docker Buildx uses : docker/setup - buildx - action@v3 - name : Log in to Container Registry uses : docker/login - action@v3 with : registry : $ { { env.REGISTRY } } username : $ { { github.actor } } password : $ { { secrets.GITHUB_TOKEN } } - name : Extract metadata id : meta uses : docker/metadata - action@v5 with : images : $ { { env.REGISTRY } } /$ { { env.IMAGE_NAME } } tags : | type=sha,prefix={{branch}}- type=semver,pattern={{version}} latest - name : Build and push Docker image uses : docker/build - push - action@v5 with : context : . push : true tags : $ { { steps.meta.outputs.tags } } labels : $ { { steps.meta.outputs.labels } } cache-from : type=gha cache-to : type=gha , mode=max deploy : needs : build runs-on : ubuntu - latest environment : production steps : - name : Deploy to production uses : appleboy/ssh - action@v1.0.0 with : host : $ { { secrets.PROD_HOST } } username : $ { { secrets.PROD_USER } } key : $ { { secrets.PROD_SSH_KEY } } script : | cd /app docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest docker-compose up -d --no-deps --build web docker image prune -f Step 3: Kubernetes Deployment Implement scalable container orchestration. k8s/deployment.yaml : apiVersion : apps/v1 kind : Deployment metadata : name : myapp namespace : production labels : app : myapp spec : replicas : 3 strategy : type : RollingUpdate rollingUpdate : maxSurge : 1 maxUnavailable : 0 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : ghcr.io/username/myapp : latest imagePullPolicy : Always ports : - containerPort : 3000 env : - name : NODE_ENV value : "production" - name : DATABASE_URL valueFrom : secretKeyRef : name : myapp - secrets key : database - url resources : requests : memory : "128Mi" cpu : "100m" limits : memory : "256Mi" cpu : "200m" livenessProbe : httpGet : path : /health port : 3000 initialDelaySeconds : 30 periodSeconds : 10 readinessProbe : httpGet : path : /ready port : 3000 initialDelaySeconds : 5 periodSeconds : 5
apiVersion : v1 kind : Service metadata : name : myapp - service namespace : production spec : selector : app : myapp ports : - protocol : TCP port : 80 targetPort : 3000 type : LoadBalancer
apiVersion : autoscaling/v2 kind : HorizontalPodAutoscaler metadata : name : myapp - hpa namespace : production spec : scaleTargetRef : apiVersion : apps/v1 kind : Deployment name : myapp minReplicas : 2 maxReplicas : 10 metrics : - type : Resource resource : name : cpu target : type : Utilization averageUtilization : 70 - type : Resource resource : name : memory target : type : Utilization averageUtilization : 80 Deployment Script (deploy.sh):
!/bin/bash
set -e
Variables
NAMESPACE
"production" IMAGE_TAG = " ${1 :- latest} " echo "Deploying myapp: ${IMAGE_TAG} to ${NAMESPACE} ..."
Apply Kubernetes manifests
kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/secrets.yaml kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml
Update image
kubectl set image deployment/myapp myapp = ghcr.io/username/myapp: ${IMAGE_TAG} -n ${NAMESPACE}
Wait for rollout
kubectl rollout status deployment/myapp -n ${NAMESPACE} --timeout = 5m
Verify
kubectl get pods -n ${NAMESPACE} -l app = myapp echo "Deployment completed successfully!" Step 4: Vercel/Netlify (Frontend) Simply deploy static sites and Next.js apps. vercel.json : { "version" : 2 , "builds" : [ { "src" : "package.json" , "use" : "@vercel/next" } ] , "env" : { "DATABASE_URL" : "@database-url" , "API_KEY" : "@api-key" } , "regions" : [ "sin1" , "icn1" ] , "headers" : [ { "source" : "/(.*)" , "headers" : [ { "key" : "X-Frame-Options" , "value" : "DENY" } , { "key" : "X-Content-Type-Options" , "value" : "nosniff" } ] } ] , "redirects" : [ { "source" : "/old-path" , "destination" : "/new-path" , "permanent" : true } ] } CLI Deployment :
Install Vercel CLI
npm i -g vercel
Login
vercel login
Deploy to preview
vercel
Deploy to production
vercel --prod
Set environment variable
vercel env add DATABASE_URL Step 5: Zero-Downtime Deployment Strategy Deploy new versions without service interruption. Blue-Green Deployment (docker-compose): version : '3.8' services : nginx : image : nginx : alpine ports : - "80:80" volumes : - ./nginx.conf : /etc/nginx/nginx.conf : ro depends_on : - app - blue - app - green app-blue : image : myapp : blue environment : - NODE_ENV=production - COLOR=blue app-green : image : myapp : green environment : - NODE_ENV=production - COLOR=green switch.sh (Blue/Green Switch):
!/bin/bash
CURRENT_COLOR
$( cat current_color.txt ) NEW_COLOR = $( [ [ " $CURRENT_COLOR " == "blue" ] ] && echo "green" || echo "blue" )
Deploy new version to inactive environment
docker-compose up -d app- ${NEW_COLOR}
Wait for health check
sleep 10
Health check
if curl -f http://localhost:8080/health ; then
Update nginx to point to new environment
sed -i "s/ ${CURRENT_COLOR} / ${NEW_COLOR} /g" nginx.conf docker-compose exec nginx nginx -s reload
Update current color
echo ${NEW_COLOR}
current_color.txt
Stop old environment after 5 minutes (rollback window)
sleep 300 docker-compose stop app- ${CURRENT_COLOR} echo "Deployment successful! Switched to ${NEW_COLOR} " else echo "Health check failed! Keeping ${CURRENT_COLOR} " docker-compose stop app- ${NEW_COLOR} exit 1 fi Output format Deployment Checklist
Deployment Checklist
Pre-Deployment
[ ] All tests passing (unit, integration, E2E)
[ ] Code review approved
[ ] Environment variables configured
[ ] Database migrations ready
[ ] Rollback plan documented
Deployment
[ ] Docker image built and tagged
[ ] Image pushed to container registry
[ ] Kubernetes manifests applied
[ ] Rolling update started
[ ] Pods healthy and ready
Post-Deployment
[ ] Health check endpoint responding
[ ] Metrics/logs monitoring active
[ ] Performance baseline established
[ ] Old pods terminated (after grace period)
- [ ] Deployment documented in changelog
- Constraints
- Required Rules (MUST)
- Health Checks
-
- Health check endpoint for all services
- app
- .
- get
- (
- '/health'
- ,
- (
- req
- ,
- res
- )
- =>
- {
- res
- .
- status
- (
- 200
- )
- .
- json
- (
- {
- status
- :
- 'ok'
- }
- )
- ;
- }
- )
- ;
- Graceful Shutdown
-
- Handle SIGTERM signal
- process
- .
- on
- (
- 'SIGTERM'
- ,
- async
- (
- )
- =>
- {
- console
- .
- log
- (
- 'SIGTERM received, shutting down gracefully'
- )
- ;
- await
- server
- .
- close
- (
- )
- ;
- await
- db
- .
- close
- (
- )
- ;
- process
- .
- exit
- (
- 0
- )
- ;
- }
- )
- ;
- Environment Variable Separation
-
- No hardcoding; use .env files
- Prohibited Rules (MUST NOT)
- No Committing Secrets
-
- Never commit API keys or passwords to Git
- No Debug Mode in Production
- :
- NODE_ENV=production
- is required
- Avoid latest tag only
-
- Use version tags (v1.0.0, sha-abc123)
- Best practices
- Multi-stage Docker builds
-
- Minimize image size
- Immutable infrastructure
-
- Redeploy instead of modifying servers
- Blue-Green deployment
-
- Zero-downtime deployment and easy rollback
- Monitoring required
-
- Prometheus, Grafana, Datadog
- References
- Docker Docs
- Kubernetes Docs
- GitHub Actions
- Vercel
- 12 Factor App
- Metadata
- Version
- Current Version
-
- 1.0.0
- Last Updated
-
- 2025-01-01
- Compatible Platforms
- Claude, ChatGPT, Gemini