Skip to content

Instantly share code, notes, and snippets.

@Simsz
Created February 7, 2025 16:57
VULTR Startup Script - Install Docker/Docker Compose/Caddy/PM2/Node and then starts the Caddy service in a docker container.

Automated Server Setup with Caddy and Docker

This script automates the setup of a web server using Caddy, Docker, PM2 and Node.js. It includes PHP-FPM support and automated SSL certificate management.

Prerequisites

  1. Debian server (will work on other distros but debian is what this was made for)
  2. For Vultr: Enable Limited User Login feature

Installation Script

#!/bin/bash
set -e
LOG_FILE="/var/log/server-setup.log"
exec 1> >(tee -a "$LOG_FILE") 2>&1

echo "Starting installation at $(date)"

# System setup
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release git

# Docker installation
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker linuxuser

# Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Create docker group
sudo groupadd -f docker

# Directory structure
sudo -u linuxuser mkdir -p /home/linuxuser/www/node.SITE_NAME.com
sudo -u linuxuser mkdir -p /home/linuxuser/www/SITE_NAME.com
sudo -u linuxuser mkdir -p /home/linuxuser/caddy/{caddy_data,caddy_config}
sudo -u linuxuser mkdir -p /home/linuxuser/www/errors

# Set permissions
sudo chown -R linuxuser:docker /home/linuxuser/www
sudo chmod 775 /home/linuxuser/www
sudo chown -R linuxuser:docker /home/linuxuser/caddy
sudo chmod 775 /home/linuxuser/caddy

# Caddy data directories
sudo chown 996:996 /home/linuxuser/caddy/caddy_data
sudo chmod 775 /home/linuxuser/caddy/caddy_data
sudo mkdir -p /home/linuxuser/caddy/caddy_data/caddy/acme-challenge
sudo chown -R 996:996 /home/linuxuser/caddy/caddy_data/caddy
sudo chmod -R 770 /home/linuxuser/caddy/caddy_data/caddy

# Caddy config directories
sudo chown linuxuser:docker /home/linuxuser/caddy/caddy_config
sudo chmod 775 /home/linuxuser/caddy/caddy_config
sudo mkdir -p /home/linuxuser/caddy/caddy_config/caddy
sudo chown root:docker /home/linuxuser/caddy/caddy_config/caddy
sudo chmod 770 /home/linuxuser/caddy/caddy_config/caddy

# Create Docker network
sudo docker network create caddy || true

# Create error page template
cat << 'EOF' | sudo -u linuxuser tee /home/linuxuser/www/errors/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Error Details</title>
    <style>
        body {
            font-family: 'Courier New', monospace;
            background: #1e1e1e;
            color: #d4d4d4;
            line-height: 1.6;
            padding: 20px;
            max-width: 1200px;
            margin: 0 auto;
        }
        .error-container {
            background: #2d2d2d;
            border-radius: 8px;
            padding: 20px;
            margin: 20px 0;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .error-header {
            color: #f44336;
            font-size: 24px;
            margin-bottom: 20px;
        }
        .error-details {
            background: #363636;
            padding: 15px;
            border-radius: 4px;
            overflow-x: auto;
        }
        .error-log {
            margin: 10px 0;
            padding: 10px;
            background: #1e1e1e;
            border-radius: 4px;
            white-space: pre-wrap;
        }
        .status-code {
            font-size: 48px;
            color: #f44336;
            margin: 20px 0;
        }
    </style>
</head>
<body>
    <div class="error-container">
        <div class="status-code">{{placeholder "http.error.status_code"}}</div>
        <div class="error-header">Error Details</div>
        <div class="error-details">
            <p><strong>Path:</strong> {{placeholder "http.request.uri"}}</p>
            <p><strong>Error:</strong> {{placeholder "http.error.message"}}</p>
            <p><strong>Method:</strong> {{placeholder "http.request.method"}}</p>
            <p><strong>Remote IP:</strong> {{placeholder "http.request.remote.host"}}</p>
            <p><strong>Time:</strong> {{placeholder "time.now"}}</p>
        </div>
        <div class="error-log">
Error Trace:
{{placeholder "http.error.trace"}}
        </div>
    </div>
</body>
</html>
EOF

# Create error page template
cat << 'EOF' | sudo -u linuxuser tee /home/linuxuser/www/SITE_NAME.com/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello World</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }
        h1 {
            color: #333;
            text-align: center;
        }
    </style>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>
EOF

# Create Caddyfile
cat << 'EOF' | sudo -u linuxuser tee /home/linuxuser/caddy/Caddyfile
(handle404) {
    handle_errors {
        root * /var/www/errors
        templates
        file_server browse
        rewrite * /index.html
    }
}

# Node.js application
# https://node.SITE_NAME.com {
#     reverse_proxy SERVER_IP:PORT
# }

https://SITE_NAME.com {
    root * /var/www/SITE_NAME.com
    file_server
    import handle404
}
EOF
sudo chmod 644 /home/linuxuser/caddy/Caddyfile

# Create docker-compose.yml
cat << 'EOF' | sudo -u linuxuser tee /home/linuxuser/caddy/docker-compose.yml
version: "3.9"

networks:
  caddy:
    driver: bridge
    external: true

services:
  caddy:
    image: caddy:2.7.6-alpine
    restart: unless-stopped
    command: caddy run --watch --config /etc/caddy/Caddyfile
    ports:
      - "80:80"
      - "443:443"
    networks:
      - caddy
    volumes:
      - "./Caddyfile:/etc/caddy/Caddyfile"
      - "./caddy_data:/data"
      - "./caddy_config:/config"
      - "/home/linuxuser/www:/var/www"
    depends_on:
      - php-fpm

  php-fpm:
    image: php:fpm-alpine
    restart: unless-stopped
    volumes:
      - "/home/linuxuser/www:/var/www"
    networks:
      - caddy
EOF
sudo chmod 644 /home/linuxuser/caddy/docker-compose.yml

# Node.js installation
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

# PM2 installation
sudo npm install -g pm2
PM2_STARTUP_CMD=$(sudo -u linuxuser pm2 startup systemd -u linuxuser --hp /home/linuxuser | grep "sudo env")
if [ ! -z "$PM2_STARTUP_CMD" ]; then
    eval "$PM2_STARTUP_CMD"
fi

# Start services
cd /home/linuxuser/caddy
sudo -u linuxuser docker-compose up -d

# Enable services
sudo systemctl enable docker
sudo -u linuxuser pm2 save

echo "Installation completed at $(date)"

Usage Instructions

  1. Replace all instances of SITE_NAME.com with your domain
  2. Save script on Vultr under Orchestration -> Scripts -> Startup Scripts
  3. For Node.js apps, uncomment the node section in Caddyfile and update SERVER_IP:PORT
  4. Place website files in /home/linuxuser/www/SITE_NAME.com
  5. Place Node.js app files in /home/linuxuser/www/node.SITE_NAME.com
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment