Docker Deploy

My Cloud-Native Deployment Stack

Cloudflare Tunnels

Secure connections, automatic SSL

Terraform

DNS management, infrastructure

Ansible

Server setup, deployments

Docker

Container orchestration

This is my personal favorite way to deploy applications. After years of managing deployments, I created this Infrastructure as Code repository to reproduce this exact setup on any server using Cloudflare tunnels for security, Terraform for infrastructure automation, Ansible for deployment orchestration, and Docker for containerization.

No exposed ports, zero downtime, enterprise-grade reliability with simple commands.

# 1. Get the system
git clone [repo] && cd docker-deploy
# 2. Setup once
./setup-server.sh
# 3. Deploy anything
./deploy.sh my-app
# Get the system
git clone [repo] && cd docker-deploy
# One-time setup
./setup-server.sh

What this does: Installs all dependencies (Docker, Terraform, Ansible), runs Terraform to create tunnels, runs Ansible to configure your server completely.

One-time setup, then you're ready to deploy anything with a Dockerfile.

Terraform handles all the infrastructure automation - creating secure tunnels and managing DNS records so you never have to touch the Cloudflare dashboard again.

# Automatically creates tunnels and DNS
resource "cloudflare_tunnel" "app_tunnel" {
account_id = var.cloudflare_account_id
name = "deployment-tunnel"
secret = random_password.tunnel_secret.result
}
resource "cloudflare_record" "app" {
zone_id = var.zone_id
name = "*.${var.domain}"
value = cloudflare_tunnel.app_tunnel.cname
type = "CNAME"
proxied = true
}

One terraform apply creates secure tunnels for all your apps with automatic SSL certificates.

Ansible handles the complete server setup AND all deployments. It configures everything from scratch, then manages zero-downtime updates with health checks and automatic rollbacks.

# Complete server setup from scratch
- name: Setup Docker networks
docker_network:
name: traefik
driver: bridge
- name: Deploy Traefik reverse proxy
docker_container:
name: traefik
image: traefik:v3.0
ports:
- "80:80"
- "443:443"
networks:
- name: traefik
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
# Zero-downtime blue-green deployment
- name: Deploy new version (green)
docker_container:
name: "{{ service_name }}-green"
image: "{{ service_name }}:{{ build_id }}"
networks:
- name: traefik
labels:
traefik.enable: "true"
traefik.http.routers.{{ service_name }}.rule: "Host(`{{ domain }}`)"
- name: Health check new version
uri:
url: "http://{{ service_name }}-green:{{ port }}/health"
register: health_check
retries: 10
delay: 3

Handles everything: server setup, Traefik deployment, webhook service, and zero-downtime app deployments.

# Deploy any Docker app
./deploy.sh my-app
# This handles everything:
# - Updates DNS (Terraform)
# - Builds and deploys container (Ansible)
# - Zero-downtime switching

Just need: A repo with a Dockerfile and knowing what port your app uses.

Optional: Configure multiple apps in services.yaml for bulk operations, or deploy one-by-one with the command.

# Deploy or update app
./deploy.sh <app>
# List all running apps
./list.sh
# Remove an app
./remove.sh <app>
# Native Docker command
docker logs <app>

The webhook service is automatically deployed and secured during setup. It receives GitHub events and triggers zero-downtime deployments automatically.

# GitHub Actions workflow
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger deployment
run: |
payload='{"repository":{"clone_url":"${{ github.event.repository.clone_url }}"},"ref_name":"${{ github.ref_name }}"}'
curl -X POST \
-H "X-GitHub-Event: push" \
-H "X-Hub-Signature-256: sha256=$(echo -n "$payload" | openssl dgst -sha256 -hmac '${{ secrets.WEBHOOK_SECRET }}')" \
-d "$payload" \
https://webhook.yourdomain.com/webhook

Push to main branch → webhook triggers → Ansible deploys → zero downtime update complete.

Traefik dashboard is automatically deployed during setup and accessible at docker-dashboard.yourdomain.com. Perfect for monitoring this deployment system.

Live Service Monitoring

  • Real-time service routing visualization
  • SSL certificate status per service
  • Health check status monitoring

Traffic & Performance

  • Request metrics per service
  • Response time monitoring
  • Traffic flow visualization

Secured with SSL and accessible through the same tunnel system as your apps. No additional configuration needed.

Here are the core scripts that power this deployment system. These examples show exactly how to build enterprise-grade automation with clean, maintainable code.

1. One-Command Server Setup

The setup-server.sh script installs everything from scratch - Docker, Terraform, Ansible, Cloudflared, and configures the entire system.

# Complete server setup automation
echo "🚀 Setting up your deployment server..."
echo "📦 Installing base packages..."
sudo apt update
sudo apt install -y curl wget git python3-pip software-properties-common
echo "🔧 Installing Terraform..."
if ! command -v terraform &> /dev/null; then
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install -y terraform
fi
echo "📦 Installing Ansible..."
sudo pip3 install ansible docker
echo "🐳 Installing Docker..."
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
echo "🌐 Installing Cloudflared..."
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb
echo "🎯 Creating Cloudflare tunnel and DNS..."
cd terraform && terraform init && terraform apply -auto-approve
echo "✅ Server setup complete!"

2. Deploy Command Script

The deploy.sh script handles the complete deployment workflow with elegant parameter validation and service management.

#!/bin/bash
# deploy.sh - deployment script
REPO_URL="$1"
DOMAIN="$2"
INTERNAL_PORT="$3"
BRANCH="${4:-main}"
CPU_LIMIT="${5:-0.5}"
MEMORY_LIMIT="${6:-512m}"
if [ -z "$REPO_URL" ] || [ -z "$DOMAIN" ] || [ -z "$INTERNAL_PORT" ]; then
echo "Usage: deploy.sh <repo-url> <domain> <internal-port> [branch] [cpu-limit] [memory-limit]"
exit 1
fi
# Generate service name from repo
SERVICE_NAME=$(basename "$REPO_URL" .git | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g')
echo "🚀 Deploying $SERVICE_NAME"
echo "📦 Repo: $REPO_URL"
echo "🌐 Domain: $DOMAIN"
echo "🔌 Internal Port: $INTERNAL_PORT"
# Update services.yaml configuration
yq eval ".services = (.services | map(select(.name != \"$SERVICE_NAME\"))) + [{
\"name\": \"$SERVICE_NAME\",
\"repo\": \"$REPO_URL\",
\"domain\": \"$DOMAIN\",
\"branch\": \"$BRANCH\",
\"internal_port\": $INTERNAL_PORT,
\"cpu_limit\": \"$CPU_LIMIT\",
\"memory_limit\": \"$MEMORY_LIMIT\"
}]" services.yaml -i
echo "📡 Creating DNS record..."
cd terraform && terraform apply -auto-approve
echo "🐳 Deploying service..."
ansible-playbook ansible/deploy-service.yml \
-e "service_name=$SERVICE_NAME" \
-e "repo_url=$REPO_URL" \
-e "domain=$DOMAIN"
echo "✅ $SERVICE_NAME deployed successfully!"
echo "🌐 Available at: https://$DOMAIN"

3. Complete Server Configuration

Ansible playbook that configures the entire server infrastructure - Docker networks, Traefik reverse proxy, and Cloudflared tunnel.

# Complete server infrastructure setup
- name: Complete server setup
hosts: localhost
become: yes
tasks:
- name: Create Docker networks
docker_network:
name: traefik
state: present
- name: Write tunnel token to file
copy:
content: "{{ tunnel_info.tunnel_token }}"
dest: /etc/cloudflared/tunnel.token
mode: '0600'
- name: Create cloudflared systemd service
copy:
content: |
[Unit]
Description=Cloudflare Tunnel
After=network.target
[Service]
ExecStart=/usr/bin/cloudflared tunnel --no-autoupdate run --token-file /etc/cloudflared/tunnel.token
Restart=always
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/cloudflared.service
- name: Generate Traefik docker-compose
copy:
content: |
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml:ro
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard-external.rule=Host(`{{ dashboard_domain }}`)"
networks:
traefik:
external: true
dest: "{{ services_dir }}/traefik/docker-compose.yml"
- name: Start Traefik
shell: cd "{{ services_dir }}/traefik" && docker compose up -d

4. Zero-Downtime Blue-Green Deployment

Advanced deployment strategy that ensures zero downtime by running two versions side-by-side with health checks and automatic rollback.

# Zero-downtime blue-green deployment
- name: Zero-downtime service deployment
hosts: localhost
vars:
services_dir: "/opt/services"
blue_suffix: "-blue"
green_suffix: "-green"
tasks:
- name: "Get current running container info"
docker_container_info:
name: "{{ service_name }}"
register: current_container
- name: "Determine deployment colors"
set_fact:
current_color: "{{ 'blue' if (current_container.exists and current_container.container.Name.endswith(blue_suffix)) else 'green' }}"
new_color: "{{ 'green' if (current_container.exists and current_container.container.Name.endswith(blue_suffix)) else 'blue' }}"
- name: "Build new Docker image"
docker_image:
name: "{{ service_name }}"
build:
path: "{{ services_dir }}/{{ service_name }}/repo"
tag: "{{ new_color }}"
force_source: yes
- name: "Start new {{ new_color }} container"
docker_container:
name: "{{ service_name }}{{ green_suffix if new_color == 'green' else blue_suffix }}"
image: "{{ service_name }}:{{ new_color }}"
state: started
restart_policy: unless-stopped
networks:
- name: traefik
cpus: "{{ cpu_limit }}"
memory: "{{ memory_limit }}"
labels:
traefik.enable: "false" # Not exposed yet
- name: "Wait for new container to be ready"
uri:
url: "http://{{ service_name }}{{ green_suffix if new_color == 'green' else blue_suffix }}:{{ internal_port }}/health"
status_code: [200, 404]
retries: 15
delay: 2
register: health_check
- name: "Update active container with Traefik labels"
docker_container:
name: "{{ service_name }}{{ green_suffix if new_color == 'green' else blue_suffix }}"
labels:
traefik.enable: "true"
traefik.http.routers.{{ service_name }}.rule: "Host(`{{ domain }}`)"
traefik.http.services.{{ service_name }}.loadbalancer.server.port: "{{ internal_port }}"
recreate: yes
- name: "Test new deployment via Traefik"
uri:
url: "http://localhost/"
headers:
Host: "{{ domain }}"
status_code: [200, 404]
retries: 10
register: traefik_check
- name: "Stop old container if deployment successful"
docker_container:
name: "{{ service_name }}{{ blue_suffix if current_color == 'blue' else green_suffix }}"
state: absent
when: traefik_check.status in [200, 404]
- name: "Clean up unused images (keep last 2)"
shell: |
docker images {{ service_name }} --format "table {% raw %}{{.Tag}}{% endraw %}\t{% raw %}{{.CreatedAt}}{% endraw %}" | grep -E "(blue|green)" | sort -k2 -r | tail -n +3 | awk '{print "{{ service_name }}:" $1}' | xargs -r docker rmi

These scripts demonstrate production-ready automation with proper error handling, health checks, and rollback capabilities. Each component works together to create a robust deployment pipeline.

Ready to Deploy?

Modern deployment stack with enterprise-grade features, simplified to 3 commands.

git clone [repo] && cd docker-deploy
./setup-server.sh
./deploy.sh my-app