Continuous Deployment for Indie Hackers: A Minimalist Pipeline
Many indie hackers and startup teams spend too much time configuring complex, expensive CD systems (like Kubernetes, GitLab CI, or paid cloud pipelines) when they only need to run a small, fast backend service.
You don't need a heavy infrastructure cluster to achieve reliable, zero-downtime continuous deployment. Let's build a **minimalist, bulletproof CD pipeline** that runs on a basic VPS!
### 1. The Single VPS Infrastructure Layout
For a cost-effective, high-speed stack, place three components on a single Linux machine:
1. **Nginx:** The edge web server handling SSL certificate handshakes (Let's Encrypt) and reverse-proxy routing.
2. **Systemd:** Manages your backend process, ensuring it auto-starts after restarts and stays alive under load.
3. **Git Hooks (or simple GitHub Actions runner):** Listens for code pushes to deploy the code.
### 2. Docker Compose Blue-Green Deployments
To deploy updates without dropping user connections, we can use Docker Compose to spin up parallel containers. Let’s review this sample configuration:
```yaml
version: '3.8'
services:
app-blue:
image: my-app:latest
ports:
- "8080:8080"
restart: always
app-green:
image: my-app:latest
ports:
- "8081:8080"
restart: always
```
When deploying:
1. Check which container is currently inactive (e.g. green).
2. Pull the latest code and start the green container (`docker compose up -d app-green`).
3. Wait for the green container's health check to report successful start.
4. Update Nginx configuration dynamically to point upstreams from port `8080` to `8081`.
5. Reload Nginx (`nginx -s reload`) - this handles connections gracefully with **zero downtime**!
6. Stop the blue container (`docker compose stop app-blue`).
### 3. Automating with a Bash Deployment Script
Let's wrap this into a shell script triggered on code push:
```bash
#!/bin/bash
set -e
echo "Deploying latest build..."
docker build -t my-app:latest .
# Blue-Green swap logic
CURRENT_PORT=$(curl -s http://localhost/api/status | jq .port)
if [ "$CURRENT_PORT" == "8080" ]; then
TARGET_PORT="8081"
TARGET_SERVICE="app-green"
SOURCE_SERVICE="app-blue"
else
TARGET_PORT="8080"
TARGET_SERVICE="app-blue"
SOURCE_SERVICE="app-green"
fi
echo "Starting $TARGET_SERVICE on port $TARGET_PORT"
docker compose up -d $TARGET_SERVICE
# Simple health check loop
sleep 5
echo "Swapping proxy target..."
sudo sed -i "s/proxy_pass http:\/\/127.0.0.1:.*/proxy_pass http:\/\/127.0.0.1:$TARGET_PORT;/g" /etc/nginx/sites-available/app
sudo systemctl reload nginx
echo "Stopping old service $SOURCE_SERVICE"
docker compose stop $SOURCE_SERVICE
echo "Deployment successful!"
```
With under 50 lines of bash script, you have created a robust, zero-downtime deployment flow that costs absolutely nothing!
// Read next
Related articles
Building Resilient Microservices with Go and gRPC
Discover the architecture secrets behind high-throughput microservices using Go and gRPC. Learn about serialization effi...
Modern Headless CMS Architectures: Best Practices
Understand the decoupled web architecture. Explore how headless CMS systems power rapid multi-channel content delivery w...
Mastering SQLite for Production Web Applications
Think SQLite is just a toy database? Think again. Learn how to configure WAL mode, handle locking, and scale SQLite to m...
// Reader response
Comments
This article has no comments yet.
// Author
Hoàng Ngô Anh Đức
Senior Full-Stack Engineer & Software Architect
Tôi là một kỹ sư phần mềm giàu kinh nghiệm chuyên thiết kế và xây dựng các hệ thống web hiện đại, scalable backend sử dụng Go, Vue.js, TypeScript và kiến trúc đám mây Cloud. Đam mê chia sẻ kiến thức kỹ thuật và tối ưu hiệu năng phần mềm.