Files
homelab/active/container_caddy/caddy.md
2026-02-25 12:15:49 -05:00

5.0 KiB

Caddy Reverse Proxy

Custom Caddy Image

This repo builds a custom caddy image with route53 DNS certbot support.

podman image pull gitea.reeseapps.com/services/caddy:latest

To upgrade the image, check the caddy-dns route53 project releases and update the Containerfile with the new version.

Install Caddy

Ansible

You'll need a secrets/Caddyfile with your caddy config.

secrets/Caddyfile example:

https://something.reeseapps.com:443 {
    reverse_proxy internal.reeselink.com:8000
}

https://something-else.reeseapps.com:443 {
    reverse_proxy internal-other.reeselink.com:8080
}

Make sure to add your route53 configuration

tls {
    dns route53 {
        access_key_id "..."
        secret_access_key "..."
        region "us-east-1"
        wait_for_route53_sync true
        skip_route53_sync_on_delete true
        route53_max_wait 2m
        max_retries 5
    }
}

The playbook limits the installer to hosts: caddy so make sure you have a caddy host in your inventory.

Now you can install the Caddy service with something like:

# Base Proxy
ansible-playbook \
-i ansible/inventory.yaml \
active/container_caddy/install_caddy_proxy.yaml

# Deskwork (AI) Proxy
ansible-playbook \
-i ansible/inventory.yaml \
active/container_caddy/install_caddy_deskwork.yaml

See ansible playbook install_caddy.yaml

Manual

As root

mkdir /etc/caddy
vim /etc/caddy/Caddyfile

Caddy will automatically provision certificates if the server DNS points to the correct IP and is accessible on the ports specifified. All you need to do is put https in the caddy conf.

Example:

# Gitea
https://gitea.reeseapps.com:443 {
    reverse_proxy podman.reeselink.com:3000
}

# Jellyfin
https://jellyfin.reeseapps.com:443 {
    reverse_proxy podman.reeselink.com:8096
}
vim /etc/containers/systemd/caddy.container
[Unit]
Description=Caddy

[Container]
AddCapability=NET_ADMIN
ContainerName=caddy
Image=docker.io/caddy:2
Network=host
SecurityLabelDisable=true
Volume=/etc/caddy:/etc/caddy
Volume=caddy_data:/data
Volume=caddy_config:/config

[Service]
Restart=always

[Install]
WantedBy=default.target
systemctl daemon-reload
systemctl restart caddy

Adding a new Caddy Record

Before you can create a Caddyfile you need records that point to your server.

You can either create them manually in your DNS provider of choice or use the provided ddns service:

  1. Update the ddns caddy records
  2. (Optional) Update the Caddyfile at active/container_caddy/secrets/Caddyfile
  3. Run the caddy ansible playbook

Logs

# Follow remote connections
podman logs -f caddy | grep -e '^{' | jq -c '.request | {remote_ip,host}'

# Filter out noisy hosts
podman logs -f caddy | grep -e '^{' | jq -c '.request | {remote_ip,host} | select(.host != "gitea.reeseapps.com")'

# Focus on user agents
podman logs -f caddy | grep -e '^{' | jq -c '
  {
    "User-Agent":   .request.headers["User-Agent"],
    remote_ip:      .request.remote_ip,
    host:           .request.host,
    status:         .status
  }
'

Caddy WAF

https://github.com/fabriziosalmi/caddy-waf

  1. Copy the rules.json to /etc/caddy/rules.json

  2. Update the Caddyfile to something like this:

    gitea.reeseapps.com:443 {
        log {
            output stdout
            format json {
                message_key     msg           # Key for the log message
                level_key      severity       # Key for the log level
                time_key       timestamp      # Key for the timestamp
                name_key       logger         # Key for the logger name
                caller_key     function       # Key for the caller information
                stacktrace_key stack          # Key for error stacktraces
                time_format    "2006-01-02 15:04:05 MST"  # RFC3339-like format
                time_local                    # Use local timezone
                duration_format "ms"          # Show durations in milliseconds
                level_format   "upper"        # Uppercase log levels
            }
        }
        route {
            waf {
                metrics_endpoint   /waf_metrics
                rule_file          rules.json
            }
    
            @wafmetrics {
                path /waf_metrics
            }
    
            handle @wafmetrics { }   # empty → let the WAF serve the metrics
    
            handle {
                reverse_proxy gitea.reeselink.com:3000
            }
        }
    }