Host study HTML files on a Hetzner server, accessible over HTTPS on my phone, with a landing page that lists all available topics.
One shared Caddy reverse proxy container handles all domains. Each project is independent.
/root/my-project/
├── caddy-proxy/ # Shared gateway (ports 80/443)
│ ├── docker-compose.yaml
│ └── Caddyfile
├── line-sticker-engine/ # Existing sticker app
│ └── docker-compose.yaml
├── my-personal-site/ # Study materials (static)
│ └── materials/
└── future-project/ # Any future site
curl "https://www.duckdns.org/update?domains=than-is-on,alinesticker&token=TOKEN&ip=STATIC_IP"Host header to decide which site to servefile_server — serves static filesfile_server browse — shows directory listing (but not when index.html exists)reverse_proxy — forwards requests to other containersdocker exec caddy caddy reload --config /etc/caddy/Caddyfiledocker network create caddy-netcaddy-net so Caddy can reach them by container nameexpose only-d = detached mode. Runs containers in background so you get your terminal back.
A browser cannot list files in a directory — JavaScript has no filesystem access. It can only make HTTP requests.
When index.html exists, Caddy serves it instead of a directory listing, even with file_server browse and even with Accept: application/json header.
Solution: Use generate-index.sh to statically build the landing page after adding new files.
.html file into /root/my-project/my-personal-site/materials/./generate-index.shdocker network create caddy-netcd /root/my-project/line-sticker-engine && docker compose downcaddy-net networkdocker compose up -dcd /root/my-project/caddy-proxy && docker compose up -ddocker ps and docker logs caddydocker-compose.yamlcaddy-net network to its servicescaddy-proxy/Caddyfiledocker exec caddy caddy reload --config /etc/caddy/Caddyfilealinesticker.duckdns.org {
handle_path /api/* {
reverse_proxy sticker_backend:8000
}
handle {
reverse_proxy sticker_frontend:8501
}
}
than-is-on.duckdns.org {
root * /srv/materials
file_server
}
Ports 80 and 443 must be open in the Hetzner firewall panel.