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.
- Debian server (will work on other distros but debian is what this was made for)
- For Vultr: Enable Limited User Login feature
#!/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)"
- Replace all instances of
SITE_NAME.com
with your domain - Save script on Vultr under
Orchestration -> Scripts -> Startup Scripts
- For Node.js apps, uncomment the node section in Caddyfile and update
SERVER_IP:PORT
- Place website files in
/home/linuxuser/www/SITE_NAME.com
- Place Node.js app files in
/home/linuxuser/www/node.SITE_NAME.com