Skip to content

Instantly share code, notes, and snippets.

@jbatch
Created February 7, 2025 11:31
Show Gist options
  • Select an option

  • Save jbatch/566d5b98db182a0b987ab0b49940061e to your computer and use it in GitHub Desktop.

Select an option

Save jbatch/566d5b98db182a0b987ab0b49940061e to your computer and use it in GitHub Desktop.
Docker Server Setup Guide

Docker Server Setup Guide

This guide outlines how to set up a secure Docker server with Cloudflare for DNS/tunneling and Caddy as a reverse proxy. This setup allows you to host multiple services securely with minimal exposed ports.

1. Initial Server Setup & Security

System Updates

First, ensure your system is up to date:

sudo apt update
sudo apt upgrade -y

UFW Firewall Setup

UFW (Uncomplicated Firewall) provides a user-friendly interface to iptables. We'll enable it with only necessary ports open:

# Install UFW if not present
sudo apt install ufw

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow necessary ports
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https

# Enable firewall
sudo ufw enable

# Verify status
sudo ufw status verbose

Fail2ban Installation

Fail2ban helps protect against brute force attacks by banning IPs that show malicious behavior:

# Install fail2ban
sudo apt install fail2ban

# Create local config
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Configure SSH jail
sudo cat << EOF > /etc/fail2ban/jail.d/ssh.local
[sshd]
enabled = true
bantime = 1h
findtime = 1h
maxretry = 3
EOF

# Start and enable fail2ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

# Verify status
sudo systemctl status fail2ban

SSH Hardening

Secure SSH access with key-based authentication and 2FA:

# Set correct SSH directory permissions
sudo chmod 700 ~/.ssh
sudo chmod 600 ~/.ssh/authorized_keys

# Install Google Authenticator
sudo apt install libpam-google-authenticator

# Configure SSH
sudo nano /etc/ssh/sshd_config

Add/modify these lines in sshd_config:

PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive

Set up Google Authenticator for your user:

google-authenticator

Follow the prompts and say 'yes' to:

  • Time-based tokens
  • Update your Google Authenticator file
  • Disallow multiple uses
  • Increase token window
  • Enable rate limiting

Add the following line to /etc/pam.d/sshd:

auth required pam_google_authenticator.so

Restart SSH service:

sudo systemctl restart sshd

System Hardening

Additional system security measures:

# Lock root console login
sudo passwd -l root

# Check for and remove any unnecessary services
sudo systemctl list-unit-files --state=enabled

2. Docker Installation

Install Docker and Docker Compose:

# Install Docker
sudo apt install docker.io

# Add your user to docker group
sudo usermod -aG docker $USER

# Install Docker Compose (if using Ubuntu's docker.io package)
sudo apt install docker-compose

# Note: Recent Docker versions (>27) use 'docker compose' (no hyphen)
# but this setup uses the separate docker-compose command

Configure GitHub Container Registry authentication:

# Create GitHub Personal Access Token with read:packages scope
# Save it to ~/.github-token

# Login as your user
cat ~/.github-token | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin

# Login as root (needed for systemd service)
sudo bash -c 'cat ~/.github-token | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin'

3. Cloudflare Setup

This setup uses Cloudflare for DNS management, SSL termination, and secure tunneling to your server.

DNS Configuration

  1. Sign up for Cloudflare and add your domain
  2. Update your domain's nameservers at your registrar to use Cloudflare's nameservers
  3. Import your existing DNS records if needed
  4. Set encryption mode to "Full" in SSL/TLS settings

Zero Trust Configuration

Set up Cloudflare Tunnel to securely expose your services:

  1. Sign up for Cloudflare Zero Trust
  2. Create a new tunnel
  3. Save the tunnel token - you'll need it for the cloudflared container

Create Basic Directory Structure

# Create base directory
sudo mkdir -p /opt/docker

# Set correct ownership so your user can manage files
sudo chown -R $USER:$USER /opt/docker

cd /opt/docker

# Create docker network
docker network create web

Set Up Cloudflared Container

Create an environment file for your tunnel token:

# cloudflared/.env
CLOUDFLARE_TUNNEL_TOKEN=<your_tunnel_token>

Note: This token can be found in your Cloudflare Zero Trust dashboard after creating a tunnel. The token requires Zone.DNS permissions.

Create directory structure:

mkdir -p cloudflared/{config,data}

Create docker-compose.yml:

version: '3.8'
services:
  cloudflared:
    container_name: cloudflared
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel run
    environment:
    - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
    volumes:
    - /opt/docker/cloudflared/config:/etc/cloudflared
    - /opt/docker/cloudflared/data:/var/log/cloudflared
    networks:
    - web
networks:
  web:
    name: web
    external: true

Configure DNS Records

In Cloudflare DNS settings:

  1. Create a CNAME record for your root domain (example.com) pointing to your tunnel
  2. Create a wildcard CNAME record (*.example.com) pointing to your tunnel
  3. In tunnel settings, add two public hostnames matching these records, both proxying to http://caddy:80

4. Caddy Setup

Caddy serves as the reverse proxy for all your services.

Create directory structure:

mkdir -p caddy/{config,data}

Create an environment file for your Cloudflare API token:

# caddy/.env
CLOUDFLARE_API_TOKEN=<your_api_token>

Note: The Caddy container may require a Cloudflare API token with Zone.Zone and Zone.DNS permissions, though this might have changed in recent versions. You might be able to reuse the same API token as cloudflared - check the current Cloudflare documentation for the most up-to-date requirements.

Create docker-compose.yml:

version: '3.8'
services:
  caddy:
    container_name: caddy
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
    - 80:80
    - 443:443
    environment:
    - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
    volumes:
    - /opt/docker/caddy/data:/data
    - /opt/docker/caddy/config:/etc/caddy
    networks:
    - web
networks:
  web:
    name: web
    external: true

Create Caddyfile in caddy/config:

{
    email your.email@example.com
    auto_https off
    http_port 80

    log {
        output file /data/access.log {
            roll_size 10mb
            roll_keep 10
        }
        format json
        level DEBUG
    }
}

(base_config) {
    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        Permissions-Policy "interest-cohort=()"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    # Compression
    encode gzip
}

:80 {
    # Example service configuration
    @service1 host service1.example.com
    handle @service1 {
        import base_config
        reverse_proxy service1:3000
    }

    # Default handler for unknown hosts
    handle {
        respond "Unknown host" 404
    }
}

5. Service Management

Service Template

Create a template for new services:

mkdir -p template/{config,data}

Create template/docker-compose.yml:

version: '3.8'
services:
  app:
    container_name: SERVICE_NAME
    image: ghcr.io/username/IMAGE:latest
    restart: unless-stopped
    networks:
    - web
networks:
  web:
    external: true

Adding New Services

  1. Copy the template directory:
cp -r template new-service
cd new-service
rm skip_service  # Enable the service
  1. Update docker-compose.yml with correct service name and image

  2. Add service to Caddyfile:

@newservice host newservice.example.com
handle @newservice {
    import base_config
    reverse_proxy new-service:3000
}

Automatic Service Management

Create start-all.sh:

#!/bin/bash
set -e
ORIGINAL_DIR=$(pwd)
cd "$(dirname "$0")"
for d in */; do
    if [ -f "${d}docker-compose.yml" ] && [ ! -f "${d}skip_service" ]; then
        echo "Starting ${d%/}..."
        (cd "$d" && docker-compose up -d)
    fi
done
cd "$ORIGINAL_DIR"

Create stop-all.sh:

#!/bin/bash
set -e
ORIGINAL_DIR=$(pwd)
cd "$(dirname "$0")"
for d in */; do
    if [ -f "${d}docker-compose.yml" ] && [ ! -f "${d}skip_service" ]; then
        echo "Stopping ${d%/}..."
        (cd "$d" && docker-compose down)
    fi
done
cd "$ORIGINAL_DIR"

Make scripts executable:

chmod +x start-all.sh stop-all.sh

Create systemd service:

sudo nano /etc/systemd/system/docker-services.service

Add content:

[Unit]
Description=Docker Compose Services
Requires=docker.service
After=docker.service network.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/docker
ExecStart=/opt/docker/start-all.sh
ExecStop=/opt/docker/stop-all.sh

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl enable docker-services
sudo systemctl start docker-services

6. Helpful Tips

Useful Aliases

Add these aliases to your ~/.bashrc or ~/.zshrc for easier management of your Docker services:

# Docker Compose shortcuts
alias up='docker-compose up'
alias down='docker-compose down'
alias restart='docker-compose restart'

# Log monitoring
alias logs="sudo tail /opt/docker/caddy/data/access.log -f"

# Service management
alias start-all="/opt/docker/start-all.sh"
alias stop-all="/opt/docker/stop-all.sh"

After adding these aliases, reload your shell configuration:

source ~/.bashrc  # or source ~/.zshrc

This completes the setup of your Docker server environment. All services will automatically start on boot and can be managed through the systemd service. New services can be easily added by copying the template and updating the Caddy configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment