Caddy in Docker Compose not working externally(?)

1. The problem I’m having:

I’m trying to migrate from my system installation of Caddy into one containerized with Docker/Compose to make building custom binaries and integrating with everything else easier.

However, when stopping my system Caddy (systemctl stop caddy) and bring up my containerized Caddy, it doesn’t appear to function and it being inaccessible on the outside even either http or https. I cannot access anything on it, Caddy itself doesn’t appear to do much in logs (it doesn’t renew/fetch certificates, etc). Kind of at a loss of what I could be poking at to see where the problem could be. I’ve even tried debug which doesn’t seem to make it spit out anything extra.

2. Error messages and/or full log output:

With my system’s Caddy

Relevant, functioning Caddy logs:

Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5050786,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"warn","ts":1744825931.514972,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"warn","ts":1744825931.5151222,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5250201,"msg":"adapted config to JSON","adapter":"caddyfile"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"warn","ts":1744825931.5250835,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":5}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5326803,"msg":"redirected default logger","from":"stderr","to":"/var/log/caddy/caddy"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5987442,"msg":"[INFO][FileStorage:/var/lib/caddy/.local/share/caddy] Lock for 'issue_cert_*.pds.synth.download' is stale (created: 2025-04-16 17:41:43.662212691 +0000 UTC, last update: 2025-04-16 17:51:54.331108266 +0000 UTC); removing then retrying: /var/lib/caddy/.local/share/caddy/locks/issue_cert_wildcard_.pds.synth.download.lock"}
Apr 16 17:52:11 synth.download systemd[1]: Started caddy.service - Caddy.

Using curl -vL:

$ curl -vL synth.download
* Host synth.download:80 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
*   Trying [2a0a:4cc0:80:108b::]:80...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 80
> GET / HTTP/1.1
> Host: synth.download
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://synth.download/
< Server: Caddy
< Date: Wed, 16 Apr 2025 18:20:05 GMT
< Content-Length: 0
<
* Closing connection
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://synth.download/'
* Host synth.download:443 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
*   Trying [2a0a:4cc0:80:108b::]:443...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=synth.download
*  start date: Feb 26 16:15:44 2025 GMT
*  expire date: May 27 16:15:43 2025 GMT
*  subjectAltName: host "synth.download" matched cert's "synth.download"
*  issuer: C=US; O=Let's Encrypt; CN=E6
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
*   Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://synth.download/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: synth.download]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: synth.download
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/2 200
< accept-ranges: bytes
< alt-svc: h3=":443"; ma=2592000
< content-type: text/html; charset=utf-8
< etag: "d97datvsaxsyka3"
< last-modified: Tue, 15 Apr 2025 16:53:38 GMT
< server: Caddy
< vary: Accept-Encoding
< content-length: 26283
< date: Wed, 16 Apr 2025 18:20:05 GMT
<
< [ in between here would be the entire html of my website ] >
* Connection #1 to host synth.download left intact

In Docker

Logs, appearing fine:

caddy-1  | {"level":"info","ts":1744827972.1217363,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
caddy-1  | {"level":"warn","ts":1744827972.1300132,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
caddy-1  | {"level":"warn","ts":1744827972.1301796,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
caddy-1  | {"level":"info","ts":1744827972.143104,"msg":"adapted config to JSON","adapter":"caddyfile"}
caddy-1  | {"level":"warn","ts":1744827972.143157,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
caddy-1  | {"level":"info","ts":1744827972.150389,"msg":"redirected default logger","from":"stderr","to":"/var/log/caddy/caddy"}

Using curl -vL:

$ curl -vL synth.download
* Host synth.download:80 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
*   Trying [2a0a:4cc0:80:108b::]:80...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 80
> GET / HTTP/1.1
> Host: synth.download
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://synth.download/
< Server: Caddy
< Date: Wed, 16 Apr 2025 18:26:16 GMT
< Content-Length: 0
<
* Closing connection
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://synth.download/'
* Host synth.download:443 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
*   Trying [2a0a:4cc0:80:108b::]:443...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS alert, internal error (592):
* OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
* Closing connection
curl: (35) OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error

3. Caddy version:

System Caddy is v2.9.1, Docker Caddy is v2.9.1

4. How I installed and ran Caddy:

a. System environment:

Ubuntu Server 24.04.2 LTS on ARM64, Caddy installed via apt from Caddy’s repositories
Docker 28.0.4, installed via apt from Docker’s repositories, Caddy built with caddyx.

b. Command:

System:

sudo systemctl start caddy # if already not started on boot

Docker:

cd /srv/docker/caddy && docker compose up -d

c. Service/unit/compose file:

System Caddy managed/started by the systemd unit included with the package.

Dockerfile:

# Builder
FROM caddy:builder AS builder

# latest version of go
#COPY --from=golang:alpine /usr/local/go/ /usr/local/go/

RUN xcaddy build \
    --with github.com/caddy-dns/porkbun \
    --with github.com/mholt/caddy-events-exec@a5d053d

# Container
FROM caddy:latest

# latest version of go
#COPY --from=golang:alpine /usr/local/go/ /usr/local/go/

# install additional packages
RUN apk add --no-cache tzdata curl

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Compose:

services:
  caddy:
    build: .
    restart: always
    #ports:
    #  - "80:80"
    #  - "443:443"
    #  - "443:443/udp"
    network_mode: host
    cap_add:
      - NET_ADMIN
    volumes:
      - ./conf:/etc/caddy
      - ./caddy_data:/data
      - ./external_certs:/mnt/external_certs
      - /var/www:/var/www
      - /var/log/caddy:/var/log/caddy

d. My complete Caddy config:

{
        debug
        email <email>

        acme_dns porkbun {
                api_key <key>
                api_secret_key <key>
        }

        events {
                on cert_obtained exec /etc/caddy/scripts/cert-obtained.sh "{event.data.identifier}" "{event.data.certificate_path}" "{event.data.private_key_path}"
        }

        log {
                output file /var/log/caddy/caddy {
                        roll_size 10MB
                        roll_keep 10
                }
        }
}

synth.download {
        root * /var/www/website
        encode zstd gzip
        file_server

        # iceshrimp.net split domain
        redir /.well-known/webfinger https://beeping.synth.download{uri} 301
        redir /.well-known/host-meta https://beeping.synth.download{uri} 301
        redir /.well-known/nodeinfo https://beeping.synth.download{uri} 301

        # redirect pub to public directory
        redir /pub /pub/
        handle_path /pub/* {
                root * /var/www/pub
                file_server browse
                encode zstd gzip
        }

        # xmpp xep-0156
        handle /.well-known/host-meta {
                @options method OPTIONS
                handle @options {
                        header Access-Control-Allow-Origin "*"
                        header Content-Type "application/xrd+xml"
                }
        }
        handle /.well-known/host-meta.json {
                @options method OPTIONS
                handle @options {
                        header Access-Control-Allow-Origin "*"
                        header Content-Type "application/xrd+xml"
                }
        }

        # 🐱
        handle_errors {
                rewrite * /{err.status_code}
                reverse_proxy https://http.cat {
                        header_up Host {upstream_hostport}
                        replace_status {err.status_code}
                }
        }

        log {
                output file /var/log/caddy/website {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# pocket id authentication
auth.synth.download {
        encode zstd gzip
        reverse_proxy /api/* 127.0.0.1:30522
        reverse_proxy /.well-known/* 127.0.0.1:30522
        reverse_proxy /* 127.0.0.1:19241

        log {
                output file /var/log/caddy/zitadel {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# notebook subdomain
# ideally this would have been a subfolder(??) (synth.download/notebook) but it Really Hated that so whatever, we're doing this now
notebook.synth.download {
        root * /var/www/quartz
        try_files {path} {path}.html {path}/ =404
        file_server
        encode zstd gzip

        log {
                output file /var/log/caddy/notebook {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# disappearing subdomain
disappearing.synth.download {
  root * /var/www/website/subdomain/disappearing
        file_server
        encode zstd gzip

        @redir {
                path /subdomain/disappearing/style.css
                path /font.css
        }
        handle @redir {
                root * /var/www/website
                encode zstd gzip
                file_server
        }

        log {
                output file /var/log/caddy/disappearing {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# vaultwarden
vault.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:60838 {
                header_up X-Real-IP {remote_host}
        }

        log {
                output file /var/log/caddy/vaultwarden {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# redlib
reddit.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:51617

        log {
                output file /var/log/caddy/redlib {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# freshrss
rss.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:27819

        log {
                output file /var/log/caddy/freshrss {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# forgejo
forged.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:41807

        log {
                output file /var/log/caddy/forgejo {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# mailserver
# we're just using caddy to generate certificates for us
mx1.synth.download {
        # Optional, can be useful for troubleshooting
        # connection to Caddy with correct certificate:
        respond "Hello DMS"
}

# xmpp
xmpp.synth.download {
        handle /ws* {
                reverse_proxy 127.0.0.1:5080 {
                        header_up X-Real-IP {remote_host}
                        header_up Host {host}
                        header_up X-Forwarded-For {remote_host}
                        header_up Upgrade {http.upgrade}
                        header_up Connection {http.connection}
                }
        }

        handle /* {
                reverse_proxy 127.0.0.1:5080 {
                        header_up X-Real-IP {remote_host}
                        header_up Host synth.download
                        header_up X-Forwarded-For {remote_host}
                }
        }
}

upload.xmpp.synth.download {
        request_body {
                max_size 100m
        }

        handle /upload/* {
                @options method OPTIONS
                handle @options {
                        header Access-Control-Allow-Origin "*"
                        header Access-Control-Allow-Methods "PUT, GET, OPTIONS, HEAD"
                        header Access-Control-Allow-Headers "Authorization, Content-Type"
                        header Access-Control-Allow-Credentials "true"
                        header Content-Length "0"
                        header Content-Type "text/plain"
                        respond 200
                }

                reverse_proxy 127.0.0.1:5050
        }
}

muc.xmpp.synth.download, proxy.xmpp.synth.download, pubsub.xmpp.synth.download {
        respond "what if it was called gyattmpp instead"
}

mta-sts.synth.download {
        root * /var/www/mta-sts
        file_server
        encode gzip

        log {
                output file /var/log/caddy/mta-sts {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# iceshrimp
beeping.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:24042

        handle_path /favicon.png {
                root * /var/www/website/assets/synth.download/favicon.png
                file_server
                encode zstd gzip
        }
        handle_path /_content/Iceshrimp.Assets.Branding/favicon.wy3b9djz5j.png {
                root * /var/www/website/assets/synth.download/favicon.png
                file_server
                encode zstd gzip
        }

        log {
                output file /var/log/caddy/iceshrimp {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# mastodon-chuckya fe for iceshrimp
masto.beeping.synth.download {
        root * /var/www/chuckya-fe
        file_server
        encode zstd gzip
        handle_path /favicon.png {
                root * /var/www/website/assets/synth.download/favicon.png
                file_server
                encode zstd gzip
        }
        try_files {path} /

        log {
                output file /var/log/caddy/mastofe {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# phanpy for iceshrimp
phanpy.beeping.synth.download {
        root * /var/www/phanpy
        file_server
        encode zstd gzip
        try_files {path} /

        log {
                output file /var/log/caddy/phanpy {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# akkoma fe for iceshrimp
akko.beeping.synth.download {
        @redir {
                path /oauth/*
                path /api/*
                path /nodeinfo/*
        }
        handle @redir {
                reverse_proxy 127.0.0.1:24042
                encode zstd gzip
        }
        handle_path /favicon.png {
                root * /var/www/website/assets/synth.download/favicon.png
                file_server
                encode zstd gzip
        }
        handle {
                header Cache-Control "max-age=3600"
                try_files {path} /
                root * /var/www/akkoma-fe
                encode zstd gzip
                file_server
        }
        handle_path /static/* {
                header Cache-Control "max-age=3600"
                root * /var/www/akkoma-fe/static
                encode zstd gzip
                file_server
        }

        log {
                output file /var/log/caddy/akkofe {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# sharkey
booping.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:60628

        log {
                output file /var/log/caddy/sharkey {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }

        handle_path /vite/CHzYc-_9.svg {
                root * /var/www/logo.svg
                file_server
                encode zstd gzip
        }

        handle_path /vite/D49xyTxq.png {
                root * /var/www/troll.png
                file_server
                encode zstd gzip
        }
}

# atproto pds
*.pds.synth.download, pds.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:24318

        log {
                output file /var/log/caddy/pds {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# ask-js
asking.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:20617

        log {
                output file /var/log/caddy/ask-js {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# upload service (dumbdrop)
upload.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:31011

        log {
                output file /var/log/caddy/upload {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# dumbwhois
whois.synth.download {
        encode zstd gzip
        reverse_proxy 127.0.0.1:22106

        log {
                output file /var/log/caddy/whois {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

# :)
adarkroom.synth.download {
        root * /var/www/adarkroom
        file_server
        encode gzip

        log {
                output file /var/log/caddy/adarkroom {
                        roll_size 10MB
                        roll_keep 10
                }
        }

        handle_path /robots.txt {
                root * /var/www/robots.txt
                file_server
                encode zstd gzip
        }
}

Hmm.. I’m not sure what’s going on. Caddy (in container) receives the request and issues a redirect to HTTPS:

But your local curl doesn’t like something about Caddy’s TLS. I don’t understand. Is your local openssl behind a few versions? What if you add -k just for testing?

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.