1. The problem I’m having:
I’m using caddy in a custom docker container and docker-compose.
I want the data and config directories to be stored in a single docker volume.
This means that I want:
data: /caddy/data
config: /caddy/config
whereas currently caddy uses /data/caddy and /config/caddy.
My problem is that I’m not able to find clear documentation on how to set these paths - I’ve found something like four different variable names that might change these settings.
My docker file is below.
2. Error messages and/or full log output:
You can see from the log files that caddy is still looking in /data/caddy
"ca":"https://acme-staging-v02.api.letsencrypt.org/directory","error":"open /data/caddy/acme/acme-staging-v02.api.letsencrypt.org-directory/users/XXXX/bsutton.json: no such file or directory"}
3. Caddy version:
FROM caddy:2.9.1-builder AS builder
4. How I installed and ran Caddy:
docker/docker-compose
a. System environment:
docker --version
Docker version 28.0.1, build 068a01e
b. Command:
PASTE OVER THIS, BETWEEN THE ``` LINES.
Please use the preview pane to ensure it looks nice.
c. Service/unit/compose file:
docker-compose.yaml
volumes:
filestore:
caddy:
services:
caddy:
container_name: caddy
image: onepub/onepub-caddy:${ONEPUB_VERSION}
restart: always
network_mode: "host"
cap_add:
- NET_ADMIN
environment:
ACME_AGREE: "true"
ACME_URL: ${ACME_URL} # staging or production
EMAIL: ${AUTH_PROVIDER_EMAIL_ADDRESS}
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
CADDY_DATA: /caddy/data
CADDY_CONFIG: /caddy/config
volumes:
# Persist certificates
- caddy:/caddy
- filestore:/opt/onepub/filestore
# Logs
- /tmp/caddy:/var/log/caddy
logging:
driver: "journald"
dockerfile
# we build our own caddy file as we need the cloudflare module.
FROM caddy:2.9.1-builder AS builder
# Set custom directories for data and config
ENV CADDY_DATA_DIR=/caddy/data
ENV CADDY_CONFIG_DIR=/caddy/config
# https://caddyserver.com/docs/modules/dns.providers.cloudflare
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/WeidiDeng/caddy-cloudflare-ip
FROM caddy:2.9.1
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
COPY config/caddy /etc/caddy
RUN mkdir -p /caddy/config
RUN mkdir /caddy/data
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
d. My complete Caddy config:
{
# Email for Let's Encrypt notifications
email {$EMAIL}
# ACME CA URL allows us to flip between production and staging.
# we default to staging.
acme_ca {$ACME_URL:"https://acme-staging-v02.api.letsencrypt.org/directory"}
# trigger the cloudflare ip module that periodically fetchs
# the list of valid cloud flare proxy IP addresses.
servers {
trusted_proxies cloudflare {
interval 12h
timeout 15s
}
}
}
# Main Domain Configuration
*.onepub.dev, onepub.dev {
tls {
# API Token required for Wild card certs
dns cloudflare {$CLOUDFLARE_API_TOKEN}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
# Remove duplicate slashes from the URI
# (e.g., /path//to///resource) but avoids the query string
@multiple_slashes path_regexp multipleSlashes ^(.*)//+(.*)$
redir @multiple_slashes {scheme}://{host}{re.multipleSlashes.1}/{re.multipleSlashes.2}{query} permanent
# Block common AI crawler User-Agents
@block_ai header_regexp User-Agent "(?i)(GPTBot|ChatGPT|Google-Extended|Claude-Web|Anthropic|Amazonbot|FacebookBot|cohere-ai|Bytespider|YouBot)"
respond @block_ai 403
# Block all PHP file requests, returning no response (similar to nginx 444)
@block_php path *.php
abort @block_php
# Redirect all .mp4 requests permanently (HTTP 301) to video subdomain
@mp4_redirect path *.mp4
redir @mp4_redirect https://video.{host}{uri} permanent
handle /mailhog/* {
basic_auth {
# password is in lastpass under 'mailhog on production'
# hash is generated by running caddy hash-password.
{$EMAIL}
}
reverse_proxy 127.0.0.1:8025
}
# Global error handling, applied only for API endpoints
handle_errors {
@api path /api/*
handle @api {
root * /etc/caddy/json
rewrite 502 /500.json
rewrite 404 /404.json
file_server
}
}
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
transport http {
read_timeout 300s
}
# Required for WebSocket support
header_up Connection {header.Connection}
header_up Upgrade {header.Upgrade}
}
encode gzip
}
# for local dev environments Note the PORT for Vaadin
# this should NEVER be exposed on an external DNS, configure /etc/hosts
local.onepub.dev {
tls {
# API Token required for Wild card certs
dns cloudflare {$CLOUDFLARE_API_TOKEN}
# We always use staging for dev so we don't hit LE rate limites.
acme_ca "https://acme-staging-v02.api.letsencrypt.org/directory"
}
header {
# disable HSTS so we can access the staging cert.
-Strict-Transport-Security
}
reverse_proxy localhost:9080
}
# Combined Video Domains Configuration
# we serve video on the video subdomain, because it would violate
# cloudflare TOS to proxy video through cloudflare
video.onepub.dev, video.beta.onepub.dev {
tls {
# API Token required for Wild card certs
dns cloudflare {$CLOUDFLARE_API_TOKEN}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
handle /*.mp4 {
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto https
transport http {
read_timeout 300s
}
# Required for WebSocket support - although I'm not certain that
# video uses it.
header_up Connection {header.Connection}
header_up Upgrade {header.Upgrade}
}
}
# Domain-based redirection (fallback)
@prod host video.onepub.dev
redir @prod https://onepub.dev{uri} permanent
redir https://beta.onepub.dev{uri} permanent
encode gzip
}