Deployment Automation — CI/CD Pipelines, Git Hooks, Docker, Ansible, Rolling Updates, and Rollbacks
Manual deployments are error-prone, slow, and stressful. Automation ensures every deployment follows the same reliable process — build, test, deploy, and verify. This guide covers CI/CD pipeline design with GitHub Actions and GitLab CI, Git hooks for pre-deployment validation, Docker container deployment strategies, Ansible playbooks for server provisioning, zero-downtime rolling updates, and automated rollback when things go wrong.
What You’ll Learn
You’ll design CI/CD pipelines that build, test, and deploy automatically, use Git hooks to prevent bad commits from reaching production, deploy Docker containers with zero downtime, provision servers with Ansible playbooks, implement rolling updates with health checks, and build automated rollback strategies. DodaZIP and Durga Antivirus Pro use automated deployment pipelines for continuous delivery of updates and security patches.
Deployment Automation Path
flowchart LR
A[Version Control] --> B[CI/CD Pipeline]
B --> C[Git Hooks]
C --> D[Docker Deploy]
D --> E[Ansible Provisioning]
E --> F[Rolling Updates]
F --> G[Rollbacks]
G --> H[Deployment Automation<br/>You are here]
style H fill:#f90,color:#fff
CI/CD Pipeline with GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
workflow_dispatch: # manual trigger
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- run: npm run lint
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
source: "dist/"
target: "/var/www/app/releases/${{ github.sha }}"
- name: Activate new release
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /var/www/app
ln -sfn releases/${{ github.sha }} current
systemctl reload nginx
curl -f http://localhost/health || exit 1
echo "Deploy successful: ${{ github.sha }}"GitLab CI Equivalent
# .gitlab-ci.yml
stages:
- test
- build
- deploy
test:
stage: test
image: node:20
script:
- npm ci
- npm test
- npm run lint
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add openssh-client curl
script:
- scp -r dist/ user@server:/var/www/app/releases/$CI_COMMIT_SHA/
- ssh user@server "
cd /var/www/app &&
ln -sfn releases/$CI_COMMIT_SHA current &&
systemctl reload nginx &&
curl -f http://localhost/health
"
only:
- mainGit Hooks for Pre-Deployment Checks
#!/bin/bash
# .git/hooks/pre-push — Prevent pushing to main without tests
BRANCH=$(git symbolic-ref HEAD 2>/dev/null | sed 's|refs/heads/||')
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "production" ]; then
echo "→ Running pre-push checks for $BRANCH..."
# Run tests
npm test 2>/dev/null || {
echo "✗ Tests failed. Push aborted."
exit 1
}
# Check for TODO/FIXME
if git diff --cached | grep -q "TODO\|FIXME"; then
echo "⚠ Warning: Unresolved TODO/FIXME in staged changes"
read -p "Continue pushing? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Ensure CHANGELOG is updated
if ! git diff --cached --name-only | grep -q "CHANGELOG"; then
echo "⚠ Warning: CHANGELOG not updated"
read -p "Continue pushing? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
echo "✓ Pre-push checks passed"
fi#!/bin/bash
# .git/hooks/post-merge — Auto-deploy after pull on production
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
if [ "$CURRENT_BRANCH" = "production" ]; then
echo "→ Production branch updated. Running deploy..."
# Rebuild
npm ci && npm run build
# Reload service
systemctl reload nginx
# Verify
if curl -f http://localhost/health; then
echo "✓ Auto-deploy successful"
else
echo "✗ Health check failed — check logs"
exit 1
fi
fi# Enable hooks project-wide (shared with team)
git config core.hooksPath .githooksDocker Deployment
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost/health || exit 1# docker-compose.yml
version: '3.8'
services:
app:
build: .
image: registry.example.com/app:${DEPLOY_TAG:-latest}
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
restart_policy:
condition: any
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- appRolling Update with Docker
#!/bin/bash
# docker-deploy.sh — Zero-downtime Docker deployment
set -euo pipefail
REGISTRY="registry.example.com"
SERVICE="app"
TAG="${1:-$(git rev-parse --short HEAD)}"
STACK_NAME="production"
echo "=== Deploying $SERVICE:$TAG ==="
# 1. Build and push
echo "[1/4] Building image..."
docker build -t "$REGISTRY/$SERVICE:$TAG" .
docker push "$REGISTRY/$SERVICE:$TAG"
# 2. Deploy with rolling update
echo "[2/4] Deploying..."
docker service update \
--image "$REGISTRY/$SERVICE:$TAG" \
--update-parallelism 1 \
--update-delay 10s \
--update-order start-first \
--update-failure-action rollback \
"${STACK_NAME}_${SERVICE}"
# 3. Monitor deployment
echo "[3/4] Monitoring rollout..."
sleep 5
docker service ps "${STACK_NAME}_${SERVICE}" \
--format "table {{.Name}}\t{{.CurrentState}}\t{{.Error}}"
# 4. Verify health
echo "[4/4] Verifying health..."
for i in $(seq 1 10); do
if curl -sf http://localhost/health > /dev/null 2>&1; then
echo "✓ Deployment successful: $TAG"
exit 0
fi
echo " Waiting... ($i/10)"
sleep 3
done
echo "✗ Health check failed — initiating rollback..."
docker service rollback "${STACK_NAME}_${SERVICE}"
exit 1Ansible Provisioning
# playbooks/deploy.yml
---
- name: Provision and deploy web application
hosts: web-servers
become: yes
vars:
app_name: dodatech-app
app_directory: /var/www/{{ app_name }}
node_version: "20.x"
tasks:
- name: Install system dependencies
apt:
name:
- nginx
- certbot
- nodejs
- git
state: present
update_cache: yes
- name: Create app directory
file:
path: "{{ app_directory }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Deploy application code
synchronize:
src: ./dist/
dest: "{{ app_directory }}/current/"
delete: yes
notify: reload nginx
- name: Configure NGINX site
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: reload nginx
- name: Enable site
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: reload nginx
- name: Set up SSL
command: >
certbot --nginx -d {{ inventory_hostname }}
--non-interactive --agree-tos -m admin@example.com
when: ssl_enabled | default(true)
notify: reload nginx
handlers:
- name: reload nginx
systemd:
name: nginx
state: reloadedRolling Update Strategies
# NGINX — active-passive (blue-green) with health checks
upstream app {
# Blue (current)
server 10.0.1.10:3000 weight=1;
# Green (staging)
# server 10.0.1.11:3000 weight=0;
# Health check
zone backend 64k;
health_check interval=5s fails=3 passes=2 uri=/health;
}
server {
location / {
proxy_pass http://app;
proxy_set_header Host $host;
}
}Blue-Green Deploy Script
#!/bin/bash
# blue-green-deploy.sh
GREEN="10.0.1.11"
BLUE="10.0.1.10"
echo "=== Blue-Green Deploy ==="
echo "Current active: BLUE ($BLUE)"
# Step 1: Deploy to green
echo "[1/4] Deploying to GREEN ($GREEN)..."
ssh deploy@$GREEN 'bash -s' < deploy-app.sh
# Step 2: Test green
echo "[2/4] Testing GREEN..."
for i in $(seq 1 5); do
if curl -sf "http://$GREEN/health" > /dev/null; then
echo " GREEN health check passed"
break
fi
echo " Waiting... ($i/5)"
sleep 3
done
# Step 3: Switch traffic
echo "[3/4] Switching traffic to GREEN..."
ssh load-balancer 'sed -i "s/server '"$BLUE"':3000;/# server '"$BLUE"':3000;/" /etc/nginx/conf.d/app.conf && sed -i "s/# server '"$GREEN"':3000;/server '"$GREEN"':3000;/" /etc/nginx/conf.d/app.conf && nginx -t && systemctl reload nginx'
# Step 4: Verify
echo "[4/4] Verifying..."
if curl -sf http://example.com/health; then
echo "✓ Blue-green switch successful"
echo " Active: GREEN ($GREEN)"
echo " Idle: BLUE ($BLUE)"
else
echo "✗ Switch failed — rolling back"
# ... rollback logic
fiRollback Strategies
#!/bin/bash
# rollback.sh — Automated rollback to previous release
RELEASE_DIR="/var/www/app/releases"
CURRENT_LINK="/var/www/app/current"
HEALTH_URL="http://localhost/health"
get_previous_release() {
ls -t "$RELEASE_DIR" | sed -n '2p'
}
rollback() {
local target_release="$1"
echo "=== Rolling back to: $target_release ==="
# Switch symlink
ln -sfn "$RELEASE_DIR/$target_release" "$CURRENT_LINK"
# Reload service
systemctl reload nginx
# Verify
if curl -sf "$HEALTH_URL"; then
echo "✓ Rollback to $target_release successful"
return 0
else
echo "✗ Rollback failed — rolling forward..."
ln -sfn "$(ls -t "$RELEASE_DIR" | sed -n '1p')" "$CURRENT_LINK"
systemctl reload nginx
return 1
fi
}
# Auto-rollback on deploy failure
if ! curl -sf "$HEALTH_URL"; then
previous=$(get_previous_release)
if [ -n "$previous" ]; then
echo "Deploy failed — auto-rolling back to $previous"
rollback "$previous"
else
echo "No previous release found — manual intervention required"
exit 1
fi
fiCommon Errors
1. Pipeline Fails Silently
CI/CD steps must check exit codes. Missing set -e in shell scripts means failures are ignored. Always use exit 1 on failure and configure pipeline to fail on any non-zero exit.
2. Rolling Update Kills All Instances
Without --update-parallelism 1 and --update-order start-first, Docker replaces all containers simultaneously, causing downtime. Always roll one instance at a time.
3. Database Migrations Without Rollback Plan
Irreversible migrations (DROP COLUMN) break rollback. Always make migrations reversible. Use down migrations: migrate -database $DB -path migrations down 1.
4. Secrets in Git History
Committing .env or API keys exposes credentials. Use .gitignore and git-secrets scanning. Rotate any keys that have been committed. Tools like git-filter-repo can purge history.
5. Health Check Not Representative
A health check that only returns 200 without testing the database or cache can pass while the app is broken. Health checks must verify actual functionality.
6. Canary Release Without Monitoring
Deploying to a subset of users without monitoring response times, error rates, and business metrics means you won’t detect problems until all users are affected.
7. Infrequent Deployments
Deploying rarely (monthly) means each release is large and risky. Frequent small deployments reduce risk — each change is smaller, rollback is easier, and the team gets faster feedback.
Practice Questions
1. What is the difference between blue-green and rolling deployment? Blue-green: two identical environments, switch traffic instantly. Rolling: gradually replace instances one by one. Blue-green has faster switchover; rolling uses less resources.
2. How do you perform a zero-downtime database migration? Use expand-migrate-contract: add new columns (expand), deploy code that uses both old and new columns, backfill data (migrate), deploy code that uses only new columns, remove old columns (contract).
3. What is the purpose of health checks in deployment? Health checks verify the new instance is serving traffic correctly before routing users to it. Failed health checks trigger automatic rollback, preventing broken deployments from reaching users.
4. How do you handle secrets in CI/CD pipelines? Use the CI/CD platform’s secret store (GitHub Secrets, GitLab CI Variables). Inject at runtime via environment variables. Never commit secrets to repositories.
5. Challenge: Design a deployment pipeline for a microservices architecture with 10 services that must be deployed in dependency order with coordinated rollbacks. Answer: Use a deployment orchestration tool (ArgoCD, Spinnaker, or a custom pipeline). Define a DAG of service dependencies. Deploy in topological order: databases first, then services with no dependents, then services that depend on them. On any failure, roll back the current and all downstream services.
Mini Project: Complete Deploy Pipeline
#!/bin/bash
# complete-deploy.sh — End-to-end deployment
set -euo pipefail
APP_NAME="${1:-myapp}"
TAG="${2:-$(git rev-parse --short HEAD)}"
SERVERS=("web1.example.com" "web2.example.com")
echo "=== Complete Deploy: $APP_NAME @ $TAG ==="
# 1. Pre-deploy checks
echo "[1/7] Pre-deploy checks..."
npm ci && npm test && npm run build
echo " ✓ Tests passed, build generated"
# 2. Tag release in git
echo "[2/7] Tagging release..."
git tag "v$TAG" && git push origin "v$TAG"
# 3. Build Docker image
echo "[3/7] Building Docker image..."
docker build -t "registry.example.com/$APP_NAME:$TAG" .
docker push "registry.example.com/$APP_NAME:$TAG"
# 4. Run DB migrations
echo "[4/7] Running migrations..."
ssh deploy@${SERVERS[0]} "cd /var/www/$APP_NAME && npm run migrate up"
# 5. Deploy to servers
echo "[5/7] Deploying..."
for SERVER in "${SERVERS[@]}"; do
echo " Deploying to $SERVER..."
ssh deploy@$SERVER "
docker pull registry.example.com/$APP_NAME:$TAG
docker service update --image registry.example.com/$APP_NAME:$TAG ${APP_NAME}_app
"
echo " ✓ $SERVER deployed"
done
# 6. Verify health
echo "[6/7] Verifying health..."
for SERVER in "${SERVERS[@]}"; do
for i in $(seq 1 10); do
if curl -sf "https://$SERVER/health"; then
echo " ✓ $SERVER healthy"
break
fi
sleep 3
done
done
# 7. Post-deploy
echo "[7/7] Post-deploy tasks..."
# Notify monitoring, update changelog, etc.
echo " ✓ Deployment complete: $TAG"
echo " Dashboard: https://status.example.com/deployments"FAQ
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro