Introduction
Automation is the backbone of modern DevOps. In this comprehensive guide, I'll walk you through building a production-grade Bash script that automates the entire deployment process for a Dockerized application on a remote Linux server. This is my journey through the HNG DevOps Stage 1 task.
What We Built
A single Bash script (deploy.sh
) that handles:
- Git repository authentication and cloning
- Remote server SSH connectivity
- Automated Docker, Docker Compose, and Nginx installation
- Docker image building and container deployment
- Nginx reverse proxy configuration
- Comprehensive logging and error handling
- Deployment validation
Why Automation Matters
Manual deployments are error-prone, time-consuming, and don't scale. By automating the entire process, we achieve:
- Consistency: Same process every time
- Speed: Deploy in minutes, not hours
- Reliability: Fewer human errors
- Repeatability: Deploy multiple times safely
- Documentation: The script IS the deployment process
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Local Machine │
│ ┌──────────────────────────────────────────────────┐ │
│ │ deploy.sh (Deployment Script) │ │
│ │ - Collects parameters │ │
│ │ - Clones Git repo │ │
│ │ - SSH into remote server │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ SSH
▼
┌─────────────────────────────────────────────────────────┐
│ Remote Server (EC2/VPS) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Step 1: Install Docker & Nginx │ │
│ │ Step 2: Clone application repo │ │
│ │ Step 3: Build Docker image │ │
│ │ Step 4: Run container │ │
│ │ Step 5: Configure Nginx proxy │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
The 8-Step Deployment Process
Step 1: Collect Parameters
The script prompts for all necessary information:
- Git repository URL
- Personal Access Token (for authentication)
- Git branch to deploy
- SSH credentials (username, IP, key path)
- Application port
This interactive approach makes the script reusable and flexible.
Enter Git Repository URL: https://github.com/yourname/your-app
Enter Personal Access Token (PAT): ••••••••••
Enter Branch name (default: main): main
Enter SSH Username: ubuntu
Enter Server IP Address: 54.123.45.67
Enter SSH Key Path: ~/.ssh/id_rsa
Enter Application Port: 3000
Step 2: Clone the Repository
The script authenticates using the PAT and clones the repository with the specified branch. This ensures we have the exact code version to deploy.
Step 3: Verify Docker Configuration
Before proceeding, the script verifies that either a Dockerfile
or docker-compose.yml
exists. This validation prevents failed deployments early.
Step 4: Test SSH Connection
A connectivity check ensures the remote server is reachable before we start any remote operations. This saves time if there's a network issue.
Step 5: Prepare Remote Environment
The script SSH's into the server and:
- Updates system packages
- Installs Docker and Docker Compose
- Installs Nginx
- Adds the user to the Docker group
- Enables and starts all services
All operations are idempotent—if Docker is already installed, the script skips it.
Step 6: Deploy the Application
The script:
- Transfers project files to the remote server
- Builds a Docker image
- Stops any old containers
- Launches a new container with the specified port
Step 7: Configure Nginx
A reverse proxy configuration is created that forwards HTTP traffic (port 80) to the Docker container's internal port. This allows external users to access your app.
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Step 8: Validate Deployment
The script confirms:
- Docker service is running
- Container is active
- Nginx is running
- Application is accessible
Key Technical Decisions
Bash Over Other Languages
Why Bash instead of Python or Go?
- Portability: Works on any Linux/Unix system
- No dependencies: Pre-installed on all servers
- Simplicity: Close to shell commands we'd run manually
- Production-tested: Used by major organizations
Error Handling
The script uses set -euo pipefail
to catch errors immediately and trap
functions to handle unexpected failures gracefully.
set -euo pipefail
trap 'handle_error $? $LINENO' ERR
Logging
All operations are logged to timestamped files:
./logs/deploy_20251020_180825.log
This provides an audit trail and helps with debugging failed deployments.
Idempotency
The script can be run multiple times safely:
- It stops and removes old containers before creating new ones
- Installation commands check if packages exist first
- Nginx configuration is overwritten, not appended
Lessons Learned
Repository Structure Matters
Keep your deployment scripts alongside your application code in the same repository. This makes it easier for others to deploy your application without hunting for separate tools. A single repository with all necessary deployment files is more maintainable.
Separate Concerns
While the deployment script lives in the app repository, you might keep a separate deployment folder locally for running deployments. This keeps your working directory organized without affecting the repository structure.
Environment Configuration
Store .env
files in your repository for non-sensitive defaults, but add them to .gitignore
for production deployments. For sensitive data (API keys, database passwords), use GitHub Secrets or environment variable management tools instead of checking them into version control.
Testing Before Deployment
Always test your endpoints after deployment:
curl http://your-server-ip/
curl http://your-server-ip/health
curl http://your-server-ip/api/info
SSH Key Management
Store SSH keys securely and never commit them to repositories. Use appropriate file permissions (chmod 600
).
Real-World Considerations
What We Didn't Include (But Should in Production)
- SSL/TLS Certificates: Use Let's Encrypt with Certbot
- Health Checks: Kubernetes-style readiness probes
- Monitoring: Prometheus, Datadog, or similar
- Scaling: Load balancers for multiple instances
- Backup Strategy: Database backups before deployments
- Rollback Plan: Ability to revert to previous versions
Security Improvements
- Use GitHub Secrets instead of manual PAT entry
- Implement IP whitelisting for SSH access
- Use IAM roles instead of long-lived credentials
- Scan Docker images for vulnerabilities
- Implement least-privilege Docker containers
CI/CD Integration
This script integrates well with GitHub Actions:
- name: Deploy
run: |
chmod +x deploy.sh
./deploy.sh
Results
After running the deployment script:
- Application successfully deployed to EC2
- Accessible at
http://3.78.183.186
- All endpoints responding correctly
- Nginx proxying traffic to container
- Logs show successful deployment
The app returns:
{
"message": "Deploy Wizard - HNG DevOps Stage 1",
"status": "running",
"version": "1.0.0",
"timestamp": "2025-10-20T17:26:58.469Z"
}
Conclusion
Building an automated deployment script teaches you the fundamentals of DevOps:
- Infrastructure as code
- Automation principles
- Server configuration
- Container orchestration basics
- Monitoring and validation
This script is a foundation you can build upon. In a real-world scenario, you'd integrate it with CI/CD pipelines, add monitoring, implement rollback strategies, and scale to multiple servers.
The journey from manual deployments to automation is what separates junior developers from seasoned DevOps engineers. Start here, and keep building.
The Implementation
The complete deployment script uses several key patterns that make it production-ready:
Error Handling: The script implements bash error handling with set -euo pipefail
and trap functions to catch failures and provide meaningful error messages at each stage.
Logging: All operations are logged to timestamped files, creating an audit trail for debugging and compliance.
Idempotency: The script safely handles re-runs by checking if services exist before installation and cleanly stopping containers before redeployment.
SSH Integration: Rather than requiring multiple manual SSH commands, the script orchestrates the entire remote deployment through a single SSH connection, executing setup and deployment steps sequentially.
Parameter Validation: User inputs are validated immediately (checking SSH keys exist, ports are numbers, URLs have correct format) to fail fast.
View the Full Code
The complete deployment script and all supporting files are available in the repository:
In the repository you'll find:
-
deploy.sh
- The main deployment script -
Dockerfile
- Container configuration -
package.json
- Node.js dependencies -
index.js
- Sample application -
README.md
- Detailed documentation -
.gitignore
- Version control configuration
Feel free to fork, modify, and use this script as a foundation for your own deployments.
Resources
Have you built your own deployment script? Share your experience in the comments below.
Top comments (0)