Skip to content

Instantly share code, notes, and snippets.

@drhema
Created February 17, 2026 08:55
Show Gist options
  • Select an option

  • Save drhema/e80577850b61912c348ababb294c2f78 to your computer and use it in GitHub Desktop.

Select an option

Save drhema/e80577850b61912c348ababb294c2f78 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -euo pipefail
# ============================================
# Dublyo PaaS — Server Provisioning Script
# Sets up Docker, Traefik v3.6, Portainer CE
# on a fresh Ubuntu 24.04 server
# ============================================
echo "========================================"
echo " Dublyo Server Setup"
echo " Docker + Traefik + Portainer"
echo "========================================"
# -------------------------------------------
# CONFIGURATION — Edit these values
# -------------------------------------------
# Cloudflare credentials (for DNS challenge SSL)
CF_API_EMAIL="${CF_API_EMAIL:-your-email@example.com}"
CF_DNS_API_TOKEN="${CF_DNS_API_TOKEN:-your-cloudflare-dns-api-token}"
# Portainer admin password (change this!)
PORTAINER_PASSWORD="${PORTAINER_PASSWORD:-$(openssl rand -base64 24)}"
# Base directory
DUBLYO_DIR="/opt/dublyo"
echo "[CONFIG] Base directory: $DUBLYO_DIR"
echo "[CONFIG] Portainer password: $PORTAINER_PASSWORD"
echo ""
echo ">>> SAVE THIS PASSWORD! <<<"
echo ""
# -------------------------------------------
# STEP 1: Disable auto-updates & clear locks
# -------------------------------------------
echo "[1/10] Disabling auto-updates..."
systemctl stop unattended-upgrades 2>/dev/null || true
systemctl disable unattended-upgrades 2>/dev/null || true
systemctl stop apt-daily.timer 2>/dev/null || true
systemctl stop apt-daily-upgrade.timer 2>/dev/null || true
systemctl disable apt-daily.timer 2>/dev/null || true
systemctl disable apt-daily-upgrade.timer 2>/dev/null || true
pkill -9 apt 2>/dev/null || true
pkill -9 dpkg 2>/dev/null || true
pkill -9 apt-get 2>/dev/null || true
rm -f /var/lib/dpkg/lock-frontend 2>/dev/null || true
rm -f /var/lib/apt/lists/lock 2>/dev/null || true
rm -f /var/lib/dpkg/lock 2>/dev/null || true
rm -f /var/cache/apt/archives/lock 2>/dev/null || true
dpkg --configure -a 2>/dev/null || true
# Helper: wait for apt lock
wait_for_apt() {
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || fuser /var/lib/dpkg/lock >/dev/null 2>&1; do
echo " Waiting for apt lock..."
sleep 3
done
}
echo "[1/10] Done"
# -------------------------------------------
# STEP 2: Update system packages
# -------------------------------------------
echo "[2/10] Updating system..."
wait_for_apt
apt-get update -q
wait_for_apt
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -q
echo "[2/10] Done"
# -------------------------------------------
# STEP 3: Install dependencies
# -------------------------------------------
echo "[3/10] Installing dependencies..."
wait_for_apt
DEBIAN_FRONTEND=noninteractive apt-get install -y -q \
ca-certificates curl gnupg jq fail2ban ufw
echo "[3/10] Done"
# -------------------------------------------
# STEP 4: Configure firewall (UFW)
# -------------------------------------------
echo "[4/10] Configuring firewall..."
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP (Traefik)
ufw allow 443/tcp # HTTPS (Traefik)
ufw allow 9443/tcp # Portainer UI
echo "y" | ufw enable
echo "[4/10] Done — Ports open: 22, 80, 443, 9443"
# -------------------------------------------
# STEP 5: Install Docker
# -------------------------------------------
echo "[5/10] Installing Docker..."
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
wait_for_apt
apt-get update -q
wait_for_apt
DEBIAN_FRONTEND=noninteractive apt-get install -y -q \
docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker
systemctl start docker
echo "[5/10] Done — $(docker --version)"
# -------------------------------------------
# STEP 6: Clean up old Docker data (if any)
# -------------------------------------------
echo "[6/10] Cleaning up old Docker data..."
docker stop traefik portainer 2>/dev/null || true
docker rm traefik portainer 2>/dev/null || true
docker volume rm portainer_data 2>/dev/null || true
docker volume rm traefik_letsencrypt 2>/dev/null || true
docker volume rm dublyo_portainer_data 2>/dev/null || true
docker volume rm dublyo_traefik_letsencrypt 2>/dev/null || true
docker network rm dublyo-public 2>/dev/null || true
echo "[6/10] Done"
# -------------------------------------------
# STEP 7: Create directory structure & configs
# -------------------------------------------
echo "[7/10] Creating directory structure..."
mkdir -p $DUBLYO_DIR/{traefik,certs,acme}
# --- Traefik docker-compose ---
cat > $DUBLYO_DIR/docker-compose.traefik.yml << 'TRAEFIKEOF'
services:
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
command:
- "--api.dashboard=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=dublyo-public"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.httpchallenge.acme.email=ssl@dublyo.com"
- "--certificatesresolvers.httpchallenge.acme.storage=/acme/acme.json"
- "--certificatesresolvers.httpchallenge.acme.httpchallenge.entrypoint=web"
- "--log.level=INFO"
environment:
- DOCKER_API_VERSION=1.44
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/dublyo/traefik:/etc/traefik/dynamic:ro
- /opt/dublyo/certs:/certs:ro
- /opt/dublyo/acme:/acme
networks:
- dublyo-public
networks:
dublyo-public:
name: dublyo-public
driver: bridge
TRAEFIKEOF
# --- Portainer docker-compose ---
cat > $DUBLYO_DIR/docker-compose.portainer.yml << 'PORTAINEREOF'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
networks:
- dublyo-public
volumes:
portainer_data:
networks:
dublyo-public:
external: true
PORTAINEREOF
# --- Cloudflare env file ---
cat > $DUBLYO_DIR/.env << ENVEOF
CF_API_EMAIL=$CF_API_EMAIL
CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
ENVEOF
echo "[7/10] Done"
# -------------------------------------------
# STEP 8: Generate SSL certificate
# -------------------------------------------
echo "[8/10] Generating self-signed origin certificate..."
# Self-signed cert for Cloudflare Full SSL mode (origin)
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout $DUBLYO_DIR/certs/origin.key \
-out $DUBLYO_DIR/certs/origin.crt \
-subj "/CN=*.dublyo.co" \
-addext "subjectAltName=DNS:*.dublyo.co,DNS:dublyo.co" \
2>/dev/null
# ACME storage file with correct permissions
touch $DUBLYO_DIR/acme/acme.json
chmod 600 $DUBLYO_DIR/acme/acme.json
# Traefik dynamic TLS config (uses self-signed cert as default)
cat > $DUBLYO_DIR/traefik/tls.yml << 'TLSEOF'
tls:
certificates:
- certFile: /certs/origin.crt
keyFile: /certs/origin.key
stores:
default:
defaultCertificate:
certFile: /certs/origin.crt
keyFile: /certs/origin.key
TLSEOF
echo "[8/10] Done"
# -------------------------------------------
# STEP 9: Start Traefik & Portainer
# -------------------------------------------
echo "[9/10] Starting Traefik..."
cd $DUBLYO_DIR
docker compose -f docker-compose.traefik.yml up -d
echo " Waiting for Docker network..."
sleep 5
echo "[9/10] Starting Portainer..."
docker compose -f docker-compose.portainer.yml up -d
echo " Waiting for Portainer to initialize..."
sleep 30
echo "[9/10] Done"
# -------------------------------------------
# STEP 10: Initialize Portainer
# -------------------------------------------
echo "[10/10] Configuring Portainer..."
# Wait for Portainer API to be ready
for i in {1..30}; do
if curl -sk https://localhost:9443/api/status | grep -q "Version"; then
echo " Portainer API is ready"
break
fi
echo " Waiting for Portainer... ($i/30)"
sleep 5
done
# Create admin user
curl -sk -X POST "https://localhost:9443/api/users/admin/init" \
-H "Content-Type: application/json" \
-d '{"Username":"admin","Password":"'"$PORTAINER_PASSWORD"'"}' > /dev/null 2>&1 || true
# Authenticate and get JWT
JWT=$(curl -sk -X POST "https://localhost:9443/api/auth" \
-H "Content-Type: application/json" \
-d '{"Username":"admin","Password":"'"$PORTAINER_PASSWORD"'"}' | jq -r '.jwt')
if [ -z "$JWT" ] || [ "$JWT" = "null" ]; then
echo "[ERROR] Failed to authenticate with Portainer"
echo " Try manually at https://SERVER_IP:9443"
exit 1
fi
# Create API key for Dublyo control plane
API_KEY=$(curl -sk -X POST "https://localhost:9443/api/users/1/tokens" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"description":"Dublyo Control Plane","password":"'"$PORTAINER_PASSWORD"'"}' | jq -r '.rawAPIKey')
# Create local Docker endpoint
ENDPOINT_ID=$(curl -sk -X POST "https://localhost:9443/api/endpoints" \
-H "X-API-Key: $API_KEY" \
-F "Name=local" -F "EndpointCreationType=1" | jq -r '.Id')
SERVER_IP=$(curl -s -4 ifconfig.me)
echo "[10/10] Done"
# -------------------------------------------
# Summary
# -------------------------------------------
echo ""
echo "========================================"
echo " Setup Complete!"
echo "========================================"
echo ""
echo " Server IP: $SERVER_IP"
echo " Portainer URL: https://$SERVER_IP:9443"
echo " Portainer User: admin"
echo " Portainer Password: $PORTAINER_PASSWORD"
echo " Portainer API Key: $API_KEY"
echo " Endpoint ID: $ENDPOINT_ID"
echo ""
echo " Docker Network: dublyo-public"
echo " Traefik: Running on :80 / :443"
echo " Portainer: Running on :9443"
echo ""
echo " Files:"
echo " $DUBLYO_DIR/docker-compose.traefik.yml"
echo " $DUBLYO_DIR/docker-compose.portainer.yml"
echo " $DUBLYO_DIR/.env (Cloudflare creds)"
echo " $DUBLYO_DIR/certs/ (SSL certs)"
echo " $DUBLYO_DIR/traefik/tls.yml"
echo " $DUBLYO_DIR/acme/acme.json"
echo ""
echo " Firewall (UFW):"
echo " 22/tcp - SSH"
echo " 80/tcp - HTTP (Traefik)"
echo " 443/tcp - HTTPS (Traefik)"
echo " 9443/tcp - Portainer"
echo ""
echo "========================================"
echo " SAVE THESE CREDENTIALS!"
echo "========================================"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment