Production-Grade Automated Docker Deployment Pipeline
StackDeployer is a production-ready, enterprise-grade Bash automation script that orchestrates the complete lifecycle of deploying Dockerized applications to remote Linux servers. Built with DevOps best practices, it provides zero-downtime deployments, comprehensive logging, automated rollback capabilities, and intelligent error handling — all through a single command execution.
- 🎯 Key Features
- 🏗️ Architecture Overview
- 🔄 Deployment Workflow
- ⚙️ Prerequisites
- 🚀 Quick Start
- 📦 Installation
- 🔧 Configuration
- 💻 Usage
- 📊 Detailed Workflow Breakdown
- 🔍 Logging & Monitoring
- 🛡️ Security Features
- 🧹 Cleanup & Maintenance
- 🐛 Troubleshooting
- 📈 Performance Optimization
- 🧪 Testing
- 📚 Advanced Usage
- 🤝 Contributing
- 📄 License
- 👨💻 Author
- PAT-based Git Authentication - Secure GitHub repository access without exposing credentials
- SSH Key Management - Automated SSH key validation with permission checks
- Credential Sanitization - Sensitive data redacted from logs
- Encrypted Communication - All remote operations over secure SSH tunnels
- Idempotent Operations - Safe to run multiple times without side effects
- Automatic Dependency Detection - Docker/Docker Compose auto-discovery
- Smart File Transfer - Rsync with delta sync for optimal bandwidth usage
- Branch-Aware Deployment - Support for multiple git branches and tags
- Comprehensive Error Handling - Trap-based error management with exit codes
- Structured Logging - Timestamped logs with severity levels (INFO, WARNING, ERROR, SUCCESS)
- Health Validation - Multi-layer deployment verification (Docker, Nginx, Container, HTTP)
- Rollback Capability - Cleanup mode for reverting deployments
- Nginx Configuration - Automated reverse proxy setup with security headers
- SSL/TLS Ready - Pre-configured HTTPS templates for certificate integration
- Load Balancing Support - Upstream configuration for horizontal scaling
- WebSocket Support - Full support for real-time applications
- Detailed Validation Reports - Post-deployment health checks with status codes
- Service Monitoring - Docker and Nginx service status verification
- Container Health Checks - Docker healthcheck inspection and reporting
- HTTP Endpoint Testing - Automated application availability verification
StackDeployer implements a modular, pipeline-based architecture that separates concerns and ensures maintainability:
┌─────────────────────────────────────────────────────────────────────┐
│ LOCAL ENVIRONMENT │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ deploy.sh │─────▶│ Git Clone │─────▶│ Pre-deployment │ │
│ │ (Script) │ │ (PAT Auth) │ │ Validation │ │
│ └─────────────┘ └──────────────┘ └────────────────┘ │
│ │ │ │
│ │ │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ SSH/Rsync │ │
│ │ File Transfer │ │
│ └────────────────┘ │
│ │ │
└────────────────────────────┼─────────────────────────────────────────┘
│
══════════▼═══════════
║ SSH Tunnel ║
║ (Encrypted) ║
══════════╦═══════════
│
┌────────────────────────────▼─────────────────────────────────────────┐
│ REMOTE SERVER (AWS EC2) │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Environment │─────▶│ Docker Build │─────▶│ Container │ │
│ │ Preparation │ │ & Deploy │ │ Health Check │ │
│ └─────────────────┘ └──────────────┘ └───────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐│ │
│ │ Nginx Reverse Proxy Layer ││ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ ││ │
│ │ │ Port 80/443 │ │ SSL/TLS │ │ Security │ ││ │
│ │ │ Listener │─▶│ Termination │─▶│ Headers │ ││ │
│ │ └──────────────┘ └──────────────┘ └───────────┘ ││ │
│ └─────────────────────────────────────────────────────┘│ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ Docker │◀────────│ Validation & │ │
│ │ Container(s) │ │ Health Checks │ │
│ └────────────────┘ └─────────────────┘ │
│ │ │
└─────────────────────────┼──────────────────────────────────────────────┘
│
▼
┌───────────────┐
│ End Users │
│ (HTTP/HTTPS) │
└───────────────┘
Each function handles a specific responsibility:
- Input validation and collection
- Git operations
- SSH connectivity
- Environment preparation
- Application deployment
- Service configuration
- Validation and monitoring
set -o errexit # Exit on command failure set -o nounset # Exit on undefined variable set -o pipefail # Exit on pipe failure
- Safe to run multiple times
- Existing containers are gracefully replaced
- Configuration files are backed up before modification
- No duplicate resource creation
- Every critical step is logged with timestamps
- Error context preserved for debugging
- Validation reports provide deployment status
- Log files retained for audit trails
1. Load Configuration
├─ Parse command-line arguments
├─ Validate environment variables
└─ Collect user inputs (interactive mode)
2. Dependency Verification
├─ Check Git installation
├─ Verify SSH client
├─ Validate Rsync availability
└─ Confirm curl/wget present
3. SSH Key Validation
├─ Check key file existence
├─ Verify read permissions
├─ Warn on insecure permissions (>600)
└─ Test key format validity
4. Repository Operations
├─ Clone repository with PAT authentication
├─ OR: Pull latest changes if already cloned
├─ Switch to specified branch
└─ Detect Dockerfile/docker-compose.yml
1. Connection Testing
├─ Attempt SSH connection (10s timeout)
├─ Retry up to 3 times with 5s intervals
├─ Verify remote command execution
└─ Fail gracefully with detailed error
2. Credential Verification
├─ Test SSH key authentication
├─ Verify user privileges
└─ Check sudo access (if required)
1. System Preparation
├─ Update package repositories
├─ Install system dependencies
└─ Configure firewall rules (if needed)
2. Docker Installation
├─ Check if Docker is installed
├─ Install Docker Engine if missing
├─ Install Docker Compose plugin
└─ Start and enable Docker service
3. Nginx Setup
├─ Install Nginx if not present
├─ Backup existing configuration
├─ Enable and start Nginx service
└─ Verify service is running
4. User Permissions
├─ Add user to docker group
├─ Apply group changes
└─ Verify Docker access without sudo
1. File Transfer
├─ Create remote project directory
├─ Rsync source files (delta sync)
├─ Exclude .git directory
└─ Preserve file permissions
2. Container Management
├─ Stop existing container (if running)
├─ Remove old container
├─ Clean up dangling images (optional)
└─ Prune unused volumes (optional)
3. Build & Deploy
├─ Build Docker image from Dockerfile
├─ OR: Run docker-compose up -d
├─ Set restart policy (unless-stopped)
└─ Map ports correctly
4. Health Verification
├─ Wait for container startup
├─ Check container status
├─ Verify healthcheck (if defined)
└─ Test application port binding
1. Nginx Configuration
├─ Generate server block
├─ Configure upstream backend
├─ Set proxy headers
├─ Add security headers
├─ Configure WebSocket support
├─ Set timeout values
└─ Define health check endpoint
2. SSL/TLS (Optional)
├─ Install certificates
├─ Configure HTTPS listener
├─ Set SSL protocols/ciphers
└─ Enable HTTP/2
3. Service Reload
├─ Test configuration syntax
├─ Backup previous config
├─ Reload Nginx gracefully
└─ Verify service status
1. Service Health Checks
├─ Docker service status ✓
├─ Docker daemon connectivity ✓
├─ Container runtime status ✓
├─ Container health status ✓
├─ Nginx service status ✓
├─ Nginx config validity ✓
├─ Port binding verification ✓
└─ HTTP endpoint test ✓
2. Deployment Report
├─ Generate status report
├─ List all check results
├─ Display access URL
└─ Show log file location
3. Error Handling
├─ Capture all failures
├─ Log error context
├─ Suggest remediation steps
└─ Exit with appropriate code
| Component | Minimum Version | Purpose |
|---|---|---|
| Bash | 5.0+ | Script execution |
| Git | 2.20+ | Repository cloning |
| SSH Client | OpenSSH 7.0+ | Remote server access |
| Rsync | 3.1.0+ | File synchronization |
| curl/wget | Any recent | HTTP requests |
Installation on Ubuntu/Debian:
sudo apt update sudo apt install -y bash git openssh-client rsync curl
Installation on macOS:
brew install bash git rsync
# SSH and curl come pre-installedInstallation on CentOS/RHEL:
sudo yum install -y bash git openssh-clients rsync curl
| Component | Requirement | Notes |
|---|---|---|
| OS | Ubuntu 20.04+, Debian 11+, or Amazon Linux 2 | 64-bit required |
| RAM | 1GB minimum, 2GB recommended | For Docker operations |
| Storage | 10GB minimum free space | For images and containers |
| CPU | 1 vCPU minimum | 2+ vCPUs recommended |
| Network | Public IP or elastic IP | For external access |
| Ports | 80, 443 (open in security groups) | HTTP/HTTPS traffic |
| Sudo Access | Yes | For package installation |
AWS EC2 Instance Types (Recommended):
- Development/Testing: t2.micro, t3.micro (Free tier eligible)
- Small Production: t3.small, t3.medium
- Production: t3.large or higher, m5.large for compute-intensive apps
- Navigate to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click "Generate new token (classic)"
- Select scopes:
- ✅
repo(Full control of private repositories) - ✅
read:org(Read organization data - if applicable)
- ✅
- Set expiration (90 days recommended)
- Copy token immediately (won't be shown again)
Token Format: ghp_ followed by 36 alphanumeric characters
# Generate SSH key pair (if needed) ssh-keygen -t ed25519 -C "deployment@yourdomain.com" -f ~/.ssh/deploy_key # Set correct permissions chmod 600 ~/.ssh/deploy_key chmod 644 ~/.ssh/deploy_key.pub # Copy public key to remote server ssh-copy-id -i ~/.ssh/deploy_key.pub ubuntu@your-ec2-ip # Test connection ssh -i ~/.ssh/deploy_key ubuntu@your-ec2-ip "echo 'Connection successful'"
AWS EC2 Specific:
- Use key pair created during instance launch
- Store
.pemfile securely - Set permissions:
chmod 400 keypair.pem
Get up and running in under 5 minutes:
# 1. Clone the repository git clone https://github.com/KoredeSec/StackDeployer.git cd StackDeployer # 2. Make script executable chmod +x deploy.sh # 3. Run deployment (interactive mode) ./deploy.sh
You'll be prompted for:
- 📦 Git repository URL
- 🔑 Personal Access Token (hidden input)
- 🌿 Branch name (default: main)
- 👤 SSH username (e.g., ubuntu)
- 🌐 Remote host IP/domain
- 🔐 SSH key path
- 🔌 Application port
git clone https://github.com/KoredeSec/StackDeployer.git
cd StackDeployer
chmod +x deploy.sh# Download latest release curl -LO https://github.com/KoredeSec/StackDeployer/archive/refs/heads/main.zip unzip main.zip cd StackDeployer-main chmod +x deploy.sh
# Download script only
curl -o deploy.sh https://raw.githubusercontent.com/KoredeSec/StackDeployer/main/deploy.sh
chmod +x deploy.shCreate a .env file in the project root for automated deployments:
# Git Configuration REPO_URL=https://github.com/your-username/your-app.git PAT=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx BRANCH=main # SSH Configuration SSH_USER=ubuntu SSH_HOST=ec2-xx-xxx-xxx-xxx.compute-1.amazonaws.com SSH_KEY=~/.ssh/your-keypair.pem # Application Configuration APP_PORT=3000 # Optional: Remote Configuration REMOTE_BASE=/home/ubuntu/deployments CONTAINER_NAME=myapp_svc
Security Note: Add .env to .gitignore to prevent credential leakage:
echo ".env" >> .gitignore
# Method 1: Export before running export $(grep -v '^#' .env | xargs) ./deploy.sh # Method 2: Source in script source .env && ./deploy.sh # Method 3: One-liner set -a; source .env; set +a; ./deploy.sh
Verify your configuration before deployment:
# Check SSH connectivity ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "echo 'Connection OK'" # Verify PAT (should show your repos) curl -H "Authorization: token $PAT" https://api.github.com/user/repos # Test rsync rsync --dry-run -av -e "ssh -i $SSH_KEY" ./ "$SSH_USER@$SSH_HOST:/tmp/test/"
Interactive Mode (Recommended for first-time users):
./deploy.sh
Non-Interactive Mode (with .env):
export $(grep -v '^#' .env | xargs) ./deploy.sh
Deploy Specific Branch:
# Set branch in .env BRANCH=staging # Or specify during interactive prompt Branch (default: main): staging
Custom Port Mapping:
# In .env or during prompt
APP_PORT=8080Deploy with Docker Compose:
# Script auto-detects docker-compose.yml # No additional flags needed ./deploy.sh
Remove all deployment artifacts:
./deploy.sh -cleanup
This will:
- ✅ Stop and remove containers
- ✅ Delete remote project directory
- ✅ Remove local cloned files
- ✅ Preserve logs for audit
Selective Cleanup:
# Manual container removal ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker rm -f container_name" # Manual directory cleanup ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "rm -rf /home/ubuntu/deployments/project"
[2025年10月21日T04:29:40+0100] [INFO] === STEP 1: Collecting input parameters ===
Git repository URL (HTTPS): https://github.com/user/app.git
Personal Access Token (PAT) - input hidden: ****
Branch (default: main): main
Remote SSH username: ubuntu
Remote SSH host (IP or domain): 52.203.xxx.xxx
SSH key path (absolute or relative): ~/.ssh/deploy.pem
Application internal container port (e.g., 3000): 3000
[2025年10月21日T04:29:45+0100] [INFO] Collected inputs: repo=https://[REDACTED]@github.com/user/app.git
Validation Checks:
- ✓ Repository URL format
- ✓ PAT non-empty
- ✓ SSH key file exists
- ✓ SSH key permissions (600 or 400)
- ✓ Port number valid (1-65535)
[2025年10月21日T04:29:46+0100] [INFO] === STEP 2: Clone or Update Repository ===
[2025年10月21日T04:29:52+0100] [INFO] Repository cloned successfully
Operations:
- Clones repository if not present locally
- Updates existing repository with latest changes
- Switches to specified branch
- Removes PAT from remote URL after clone
[2025年10月21日T04:29:53+0100] [INFO] === STEP 3: Checking project Docker setup ===
[2025年10月21日T04:29:53+0100] [INFO] Dockerfile found
Detection Logic:
if Dockerfile exists: USE_DOCKER_COMPOSE=0 elif docker-compose.yml exists: USE_DOCKER_COMPOSE=1 else: ERROR: No Docker configuration found
[2025年10月21日T04:29:54+0100] [INFO] === STEP 4: Testing SSH connectivity ===
[2025年10月21日T04:29:55+0100] [INFO] SSH connectivity check attempt 1/3
[2025年10月21日T04:29:56+0100] [SUCCESS] SSH connectivity verified successfully
Retry Logic:
- 3 attempts with 5-second intervals
- 10-second connection timeout
- Detailed error reporting on failure
[2025年10月21日T04:29:57+0100] [INFO] === STEP 5: Transferring project to remote ===
sending incremental file list
./
Dockerfile
app.js
package.json
sent 42,350 bytes received 156 bytes 28,337.33 bytes/sec
[2025年10月21日T04:30:02+0100] [INFO] Project transferred successfully
Rsync Efficiency:
- Delta transfer algorithm (only changed files)
- Compression during transfer
- Preserves permissions and timestamps
- Excludes .git directory (reduces transfer size)
[2025年10月21日T04:30:03+0100] [INFO] === STEP 6: Preparing remote environment ===
Updating system...
Installing Docker...
Installing Nginx...
Adding user to docker group...
[2025年10月21日T04:30:20+0100] [INFO] Remote environment prepared
Installation Checks:
- Skips if Docker already installed
- Skips if Nginx already running
- Idempotent group membership
[2025年10月21日T04:30:21+0100] [INFO] === STEP 7: Deploying Dockerized Application ===
Stopping existing container (if any)...
Building Docker image...
Starting new container...
Container ID: a8f3d9c2b1e4
[2025年10月21日T04:30:35+0100] [INFO] Application deployed successfully
Container Management:
# Stop old container docker rm -f app_svc 2>/dev/null || true # Build new image docker build -t app_svc:latest . # Run with automatic restart docker run -d \ --name app_svc \ -p 3000:3000 \ --restart unless-stopped \ app_svc:latest
[2025年10月21日T04:30:36+0100] [INFO] === STEP 8: Configuring Nginx Reverse Proxy ===
Creating Nginx configuration...
Testing configuration...
nginx: configuration file /etc/nginx/nginx.conf test is successful
Reloading Nginx...
[2025年10月21日T04:30:40+0100] [SUCCESS] Nginx configured and reloaded successfully
Generated Configuration:
upstream app_backend { server 127.0.0.1:3000 fail_timeout=10s max_fails=3; } server { listen 80 default_server; listen [::]:80 default_server; server_name _; location / { proxy_pass http://app_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # ... additional headers } }
[2025年10月21日T04:30:41+0100] [INFO] === STEP 9: Validating Deployment ===
================================================
🔍 DEPLOYMENT VALIDATION REPORT
================================================
📦 Docker Service Status Check:
✅ Docker service is running
Docker service check: PASSED
🐳 Container Status Check:
✅ Container 'app_svc' is running
Status: running
Container status check: PASSED
🌐 Nginx Service Status Check:
✅ Nginx service is running
Nginx service check: PASSED
⚙️ Nginx Configuration Test:
✅ Nginx configuration is valid
Nginx configuration check: PASSED
🔌 Application Port Check:
✅ Application is listening on port 3000
Port check: PASSED
🌍 Local HTTP Test:
✅ Application responding (HTTP 200)
HTTP test: PASSED
================================================
✅ VALIDATION COMPLETE - ALL CHECKS PASSED
================================================
[2025年10月21日T04:30:45+0100] [SUCCESS] Deployment validation completed successfully
Logs are stored in ./logs/ with timestamped filenames:
logs/
├── deploy_20251021_042940.log (Latest deployment)
├── deploy_20251020_153022.log
└── deploy_20251019_091545.log
[TIMESTAMP] [LEVEL] MESSAGE
Levels:
- [INFO] : General information
- [SUCCESS] : Successful operations
- [WARNING] : Non-critical issues
- [ERROR] : Failures requiring attention
2025年10月21日T04:29:40+0100 [INFO] === STEP 1: Collecting input parameters ===
2025年10月21日T04:29:45+0100 [INFO] Collected inputs: repo=https://[REDACTED]@github.com/user/app.git
2025年10月21日T04:29:46+0100 [INFO] === STEP 2: Clone or Update Repository ===
2025年10月21日T04:29:52+0100 [INFO] Repository cloned successfully
2025年10月21日T04:30:45+0100 [SUCCESS] Deployment completed successfully at 2025年10月21日T04:30:45+0100
# View latest log tail -f logs/deploy_$(ls -t logs/ | head -1) # View specific log less logs/deploy_20251021_042940.log # Search for errors grep ERROR logs/deploy_*.log # Count successful deployments grep "SUCCESS.*Deployment completed" logs/*.log | wc -l
Implement log rotation to prevent disk space issues:
# Create logrotate configuration sudo tee /etc/logrotate.d/stackdeployer << EOF /path/to/StackDeployer/logs/*.log { daily rotate 30 compress delaycompress missingok notifempty } EOF
- PAT never written to logs (sanitized)
- SSH keys validated for correct permissions (600/400)
- Environment variables for sensitive data
.envexcluded from version control
# Hardened SSH options -o StrictHostKeyChecking=accept-new # First-time connection -o BatchMode=yes # Non-interactive -o ConnectTimeout=10 # Timeout for connections
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Container runs with restart policy --restart unless-stopped # No privileged mode # No host network mode # Explicit port mapping (-p)
- Repository URL format verification
- SSH key existence and readability checks
- Port range validation (1-65535)
- Branch name sanitization
# Full cleanup (containers + files)
./deploy.sh -cleanupRemove Stopped Containers:
ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker container prune -f"
Remove Unused Images:
ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker image prune -a -f"
Remove Unused Volumes:
ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker volume prune -f"
Clean Build Cache:
ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker builder prune -a -f"
# List backups ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "ls -lh /etc/nginx/sites-available/default.bak_*" # Remove old backups (keep last 5) ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" \ "cd /etc/nginx/sites-available && ls -t default.bak_* | tail -n +6 | xargs -r sudo rm"
# Remove logs older than 30 days find logs/ -name "*.log" -mtime +30 -delete # Archive old logs tar -czf logs_archive_$(date +%Y%m).tar.gz logs/*.log mv logs_archive_*.tar.gz ~/archives/ # Keep only last 10 logs ls -t logs/*.log | tail -n +11 | xargs rm -f
Symptom:
[ERROR] SSH connection failed after 3 attempts
Solutions:
# Check if port 22 is open in security group aws ec2 describe-security-groups --group-ids sg-xxxxx # Verify SSH service is running ssh -v -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" # Check correct username (ubuntu for Ubuntu, ec2-user for Amazon Linux) ssh -i "$SSH_KEY" ubuntu@$SSH_HOST ssh -i "$SSH_KEY" ec2-user@$SSH_HOST # Verify key permissions chmod 600 ~/.ssh/your-key.pem
Symptom:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic)
Solutions:
# Ensure correct key file ssh-add -l # Add key to ssh-agent eval $(ssh-agent) ssh-add ~/.ssh/your-key.pem # Verify public key on remote server ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "cat ~/.ssh/authorized_keys"
Symptom:
[ERROR] Docker build failed
Solutions:
# Check Dockerfile syntax locally docker build -t test . # SSH into server and check Docker logs ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" docker logs container_name # Check disk space on remote server df -h # Clean up Docker resources docker system prune -a -f
Symptom:
Error: bind: address already in use
Solutions:
# Find process using the port ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo lsof -i :3000" # Kill the process ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo kill -9 PID" # Or use different port in APP_PORT variable APP_PORT=3001
Symptom:
nginx: [emerg] unexpected "}" in /etc/nginx/sites-available/default
Solutions:
# Test configuration manually ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo nginx -t" # View configuration file ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo cat /etc/nginx/sites-available/default" # Restore from backup ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" \ "sudo cp /etc/nginx/sites-available/default.bak_* /etc/nginx/sites-available/default" # Reload Nginx ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo systemctl reload nginx"
Symptom:
Container health: unhealthy
Solutions:
# Check container logs ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker logs container_name --tail 100" # Inspect health check ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" \ "docker inspect --format='{{json .State.Health}}' container_name | jq" # Enter container for debugging ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker exec -it container_name /bin/bash" # Check application logs inside container docker exec container_name cat /var/log/app.log
Symptom:
rsync: connection unexpectedly closed
Solutions:
# Test rsync with verbose output rsync -avz --progress -e "ssh -i $SSH_KEY" ./ "$SSH_USER@$SSH_HOST:/tmp/test/" # Check available disk space ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "df -h" # Try with compression disabled (for high latency connections) rsync -az --no-compress ... # Exclude large files/directories rsync --exclude 'node_modules' --exclude '*.log' ...
Enable detailed output for troubleshooting:
# Add debug flags at the top of deploy.sh set -x # Print commands before execution # Run with bash debug bash -x deploy.sh # Capture full output ./deploy.sh 2>&1 | tee debug_$(date +%Y%m%d_%H%M%S).log
Create a standalone health check:
#!/bin/bash # health_check.sh SSH_KEY="1ドル" SSH_USER="2ドル" SSH_HOST="3ドル" CONTAINER="4ドル" echo "=== Docker Service ===" ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "systemctl status docker --no-pager" echo -e "\n=== Container Status ===" ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker ps -a --filter name=$CONTAINER" echo -e "\n=== Container Logs (last 20 lines) ===" ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker logs $CONTAINER --tail 20" echo -e "\n=== Nginx Status ===" ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "systemctl status nginx --no-pager" echo -e "\n=== Port Listening ===" ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo netstat -tulpn | grep LISTEN"
Use .rsyncignore:
# Create .rsyncignore file cat > .rsyncignore << EOF node_modules/ .git/ *.log .env dist/ build/ coverage/ EOF # Update rsync command in script rsync -az --exclude-from=.rsyncignore ...
Enable Compression:
# For slow networks rsync -avz -e "ssh -i $SSH_KEY -C" ...
Multi-stage Dockerfile:
# Build stage FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production # Production stage FROM node:18-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY . . EXPOSE 3000 CMD ["node", "app.js"]
Use .dockerignore:
node_modules
npm-debug.log
.git
.gitignore
.env
*.md
.DS_Store
# Build with cache docker build --cache-from myapp:latest -t myapp:latest . # Use BuildKit for better caching DOCKER_BUILDKIT=1 docker build -t myapp:latest .
For multiple deployments:
#!/bin/bash # parallel_deploy.sh declare -a servers=("server1.com" "server2.com" "server3.com") for server in "${servers[@]}"; do ( export SSH_HOST="$server" ./deploy.sh ) & done wait echo "All deployments completed"
# In nginx.conf worker_processes auto; worker_connections 1024; # Enable gzip compression gzip on; gzip_vary on; gzip_types text/plain text/css application/json application/javascript; # Enable caching proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m; proxy_cache app_cache; proxy_cache_valid 200 1h;
1. Test Script Syntax:
bash -n deploy.sh # Check syntax without execution shellcheck deploy.sh # Static analysis (install: apt install shellcheck)
2. Test Docker Build Locally:
# Clone repo git clone https://github.com/user/app.git cd app # Build image docker build -t test-app . # Run container docker run -d -p 3000:3000 --name test-app test-app # Test endpoint curl http://localhost:3000 # Cleanup docker rm -f test-app
3. Test SSH Connection:
# Test basic connection ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "echo 'SSH OK'" # Test Docker access ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker ps" # Test sudo access ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo whoami"
Create Test Suite:
#!/bin/bash # test_deployment.sh set -e echo "=== Running Deployment Tests ===" # Test 1: Script exists and is executable test -x deploy.sh && echo "✓ Script is executable" || exit 1 # Test 2: Required commands available for cmd in git ssh rsync curl; do command -v $cmd >/dev/null 2>&1 && echo "✓ $cmd found" || exit 1 done # Test 3: SSH connectivity ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "exit" && echo "✓ SSH connected" || exit 1 # Test 4: Remote Docker available ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker info >/dev/null 2>&1" && \ echo "✓ Docker available" || exit 1 # Test 5: Remote Nginx available ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "nginx -v 2>&1" && \ echo "✓ Nginx available" || exit 1 echo "=== All Tests Passed ==="
Automated Health Check:
#!/bin/bash # post_deploy_test.sh APP_URL="http://$SSH_HOST" # Test 1: HTTP 200 response HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$APP_URL") if [[ "$HTTP_CODE" == "200" ]]; then echo "✓ Application responding (HTTP 200)" else echo "✗ Application not responding (HTTP $HTTP_CODE)" exit 1 fi # Test 2: Response time < 2 seconds RESPONSE_TIME=$(curl -o /dev/null -s -w '%{time_total}' "$APP_URL") if (( $(echo "$RESPONSE_TIME < 2" | bc -l) )); then echo "✓ Response time acceptable ($RESPONSE_TIME seconds)" else echo "⚠ Slow response time ($RESPONSE_TIME seconds)" fi # Test 3: Container running ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" \ "docker ps --filter name=$CONTAINER_NAME --format '{{.Status}}' | grep -q Up" && \ echo "✓ Container is running" || exit 1 # Test 4: No critical errors in logs ERROR_COUNT=$(ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" \ "docker logs $CONTAINER_NAME 2>&1 | grep -i 'error\|critical' | wc -l") if [[ "$ERROR_COUNT" -eq 0 ]]; then echo "✓ No critical errors in logs" else echo "⚠ Found $ERROR_COUNT error(s) in logs" fi echo "=== Post-Deployment Tests Complete ==="
Create environment-specific configs:
# .env.production REPO_URL=https://github.com/user/app.git BRANCH=main SSH_HOST=prod-server.com APP_PORT=3000 # .env.staging REPO_URL=https://github.com/user/app.git BRANCH=staging SSH_HOST=staging-server.com APP_PORT=3001 # Deploy to specific environment export $(grep -v '^#' .env.staging | xargs) ./deploy.sh
#!/bin/bash # blue_green_deploy.sh # Deploy to green environment export CONTAINER_NAME="app_green" export APP_PORT=3001 ./deploy.sh # Test green environment if curl -f http://$SSH_HOST:3001/health; then echo "Green environment healthy" # Switch Nginx to green ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" \ "sudo sed -i 's/127.0.0.1:3000/127.0.0.1:3001/' /etc/nginx/sites-available/default" ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "sudo systemctl reload nginx" # Stop blue environment ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" "docker stop app_blue" else echo "Green environment unhealthy, keeping blue active" exit 1 fi
# Add to deploy.sh before container start remote_run_migrations() { log "Running database migrations..." ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" bash <<EOF cd "$REMOTE_PROJECT_DIR" docker run --rm \ --network host \ -e DATABASE_URL="\$DATABASE_URL" \ ${CONTAINER_NAME}:latest \ npm run migrate EOF log "Migrations completed" }
# Add notification function send_notification() { local status="1ドル" local message="2ドル" local webhook_url="YOUR_WEBHOOK_URL" curl -X POST "$webhook_url" \ -H 'Content-Type: application/json' \ -d "{\"text\": \"🚀 Deployment $status: $message\"}" } # Call in main function if [[ $? -eq 0 ]]; then send_notification "SUCCESS" "App deployed to $SSH_HOST" else send_notification "FAILED" "Deployment to $SSH_HOST failed" fi
backup_current_deployment() { log "Creating backup of current deployment..." ssh -i "$SSH_KEY" "$SSH_USER@$SSH_HOST" bash <<EOF if [[ -d "$REMOTE_PROJECT_DIR" ]]; then BACKUP_DIR="/home/ubuntu/backups/\$(date +%Y%m%d_%H%M%S)" mkdir -p "\$BACKUP_DIR" cp -r "$REMOTE_PROJECT_DIR" "\$BACKUP_DIR/" echo "Backup created at \$BACKUP_DIR" fi EOF log_success "Backup completed" }
# Install AWS CLI on local machine # Configure credentials: aws configure retrieve_secrets() { log "Retrieving secrets from AWS Secrets Manager..." SECRET_JSON=$(aws secretsmanager get-secret-value \ --secret-id prod/app/credentials \ --query SecretString \ --output text) export DB_PASSWORD=$(echo "$SECRET_JSON" | jq -r '.db_password') export API_KEY=$(echo "$SECRET_JSON" | jq -r '.api_key') log_success "Secrets retrieved" }
We welcome contributions! Here's how you can help:
- Check existing issues first
- Provide detailed description
- Include relevant logs
- Specify your environment (OS, Bash version, etc.)
Issue Template:
**Description:** Brief description of the issue **Steps to Reproduce:** 1. Step one 2. Step two 3. Step three **Expected Behavior:** What should happen **Actual Behavior:** What actually happens **Environment:** - OS: Ubuntu 22.04 - Bash: 5.1.16 - Docker: 24.0.5 **Logs:**
Paste relevant log excerpts
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test thoroughly
- Commit with clear messages (
git commit -m 'Add amazing feature') - Push to your fork (
git push origin feature/amazing-feature) - Open a Pull Request
PR Checklist:
- Code follows existing style
- All functions have comments
- Tested on Ubuntu 20.04+
- No hardcoded credentials
- Documentation updated
- Changelog updated
# Clone your fork git clone https://github.com/YOUR_USERNAME/StackDeployer.git cd StackDeployer # Add upstream remote git remote add upstream https://github.com/KoredeSec/StackDeployer.git # Create feature branch git checkout -b feature/my-feature # Make changes and test ./deploy.sh # Push and create PR git push origin feature/my-feature
├── Lines of Code: 600+
├── Functions: 20+
├── Deployment Steps: 9
├── Validation Checks: 7
├── Error Handlers: 3
├── Logging Levels: 4
└── Supported Platforms: Ubuntu, Debian, Amazon Linux ← Correct
| Practice | Implementation | Status |
|---|---|---|
| Infrastructure as Code | Bash script automation | ✅ |
| Idempotency | Safe re-runs without side effects | ✅ |
| Error Handling | Comprehensive trap and validation | ✅ |
| Logging | Structured logs with timestamps | ✅ |
| Security | SSH keys, PAT, credential sanitization | ✅ |
| Modularity | Function-based architecture | ✅ |
| Documentation | Comprehensive README | ✅ |
| Version Control | Git-based workflow | ✅ |
| Automated Testing | Pre/post deployment checks | ✅ |
| Rollback Capability | Cleanup mode available | ✅ |
StackDeployer/
├── deploy.sh # Main deployment script
├── README.md # This file
├── .env.example # Environment template
├── .gitignore # Git exclusions
├── LICENSE # MIT License
├── server.js # Node.js app entry point
├── package.json # Node.js dependencies and scripts
├── Dockerfile # Docker build instructions
└── logs/
├── deploy_20251021_042940.log
├── deploy_20251020_153022.log
- ShellCheck - Shell script analysis
- Docker Hub - Container registry
- Let's Encrypt - Free SSL certificates
MIT License
Copyright (c) 2025 Ibrahim Yusuf (Tory)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Ibrahim Yusuf (Tory)
🎓 President – NACSS_UNIOSUN (Nigeria Association Of CyberSecurity Students, Osun State University)
🔐 Certifications: Certified in Cybersecurity (ISC2 CC) | Microsoft SC-200
💼 Focus: Cloud Architecture, DevSecOps, Automation, Threat Intel, Cybersecurity
- 🐙 GitHub: @KoredeSec
- ✍️ Medium: Ibrahim Yusuf
- 🐦 X (Twitter): @KoredeSec
- 💼 LinkedIn: Restricted currently
- AdwareDetector AdwareDetector
- threat-intel-aggregatorthreat-intel-aggregator
- azure-sentinel-home-soc azure-sentinel-home-soc
Special thanks to:
- HNG Internship for the inspiration
- DevOps practitioners who shared best practices
If you find this project useful, please consider giving it a star on GitHub!
Built with ❤️ for the DevOps Community
Made in Nigeria 🇳🇬 | Open Source | MIT Licensed