Setting Up Hugo with Docker
I’m documenting my setup of Hugo and Docker on a cloud machine I recently rented. This guide covers the complete stack: Hugo static site generator, Docker containerization, and Caddy reverse proxy with Let’s Encrypt HTTPS. The steps are:
-
Container Service Setup — Define Hugo in docker-compose with klakegg/hugo image. Mount source directory at
/srcand logs at/tmp/logs/. Expose port 3000 for local access. -
Port Mapping (Host:Container) — Map
127.0.0.1:3000:3000exposes container port 3000 on host localhost. First port is host port, second is container port. Restrict to localhost (127.0.0.1) for security. Access athttp://127.0.0.1:3000from host machine. -
File Mapping (Volumes) — Mount
./hugo:/srcmaps entire Hugo project from host to container. Container reads source files from/src. Changes on host are immediately visible in container. Mount./data/hugo/logs/:/tmp/logs/persists logs on host filesystem. Volumes enable live editing without rebuilding container.
hugo:
image: klakegg/hugo:latest
container_name: hugo
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./hugo:/src
- ./data/hugo/logs/:/tmp/logs/
command: server --bind="0.0.0.0" --port=3000 --debug --log --verboseLog --verbose --buildDrafts --buildFuture --baseUrl="https://www.example.com" --appendPort=false --logFile=/tmp/logs/hugo.log --config=config.toml --configDir=/src/ --source=/src/ --themesDir=/src/themes/
environment:
USER: user
-
Essential Flags — Enable debugging with
--debug --log --verboseLog --verbose. Build drafts and future posts with--buildDrafts --buildFuture. Set base URL, config path, and theme directory explicitly. -
Log Mounting — Store logs on host filesystem at
./data/hugo/logs/. Point container logs to/tmp/logs/hugo.log. Persist logs across restarts for debugging issues. -
Inspecting Logs — View live logs with
docker-compose logs -f hugo. Check historical logs withcat ./data/hugo/logs/hugo.log. Search for errors withgrep -i error ./data/hugo/logs/hugo.log. -
Debugging Build Errors — Enable all verbose flags to see what Hugo processes. Check if theme exists in
./hugo/themes/. Verify config.toml syntax withhugo config. Test locally before pushing changes. -
Verifying Functionality — Access site at
http://127.0.0.1:3000. Check network access withcurl http://127.0.0.1:3000. Verify content renders by navigating to/blogand checking post dates. -
Theme Setup — Clone theme to
./hugo/themes/. Set theme name inconfig.toml. Mount themes directory in docker-compose with--themesDir=/src/themes/. Verify theme loads in logs without warnings. -
Restart and Persistence — Use
restart: unless-stoppedto keep container running. Stop withdocker-compose stop hugo. Start withdocker-compose start hugo. Logs persist in mounted volume across restarts. -
Production Considerations — Remove
--debug --verboseflags in production. Build site once instead of continuous server withhugo build. Mount only necessary directories for security. Use HTTPS with proper base URL configuration.
Caddy Reverse Proxy Setup
-
Reverse Proxy Configuration — Use Caddy to proxy requests from domain to Hugo container. Define in Caddyfile. Routes HTTP/HTTPS traffic to Hugo on port 3000. Handles SSL certificates automatically with Let’s Encrypt.
-
Caddy Port Mapping — Expose
0.0.0.0:8080:80and0.0.0.0:8443:443to accept traffic from all interfaces. Host ports 8080 (HTTP) and 8443 (HTTPS) map to container ports 80/443. Caddy listens on container ports 80/443, accessible via host ports 8080/8443. All traffic flows through Caddy before reaching Hugo. -
Caddy Volume Mounts — Mount
./Caddyfile:/etc/caddy/Caddyfileprovides configuration from host. Mount./caddy_data:/datastores HTTPS certificates and Caddy state on host. Mount./caddy_config:/configpersists Caddy configuration across restarts. Mount./data/caddy/logs/:/data/logs/saves access logs on host filesystem for monitoring and debugging.
caddy:
image: caddy:latest
container_name: caddy-reverse-proxy
restart: unless-stopped
ports:
- "8080:80"
- "8443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./caddy_data:/data
- ./caddy_config:/config
- ./data/caddy/logs/:/data/logs/
environment:
MEMOS_DOMAIN: ${MEMOS_DOMAIN}
dns:
- 8.8.8.8
- 8.8.4.4
- Caddyfile Example — Map domain to Hugo container. Use reverse_proxy directive pointing to
hugo:3000. Caddy auto-discovers container via Docker network.
example.com {
reverse_proxy hugo:3000
}
www.example.com {
reverse_proxy hugo:3000
}
-
SSL/TLS Handling — Caddy automatically provisions and renews HTTPS certificates. No manual certificate management needed. Redirects HTTP to HTTPS by default.
-
Logging Access Requests — Mount logs directory at
./data/caddy/logs/. Access logs track all HTTP requests. Monitor traffic and debug connectivity issues through logs. -
Service Discovery — Caddy and Hugo communicate via Docker network using container names. Use
hugo:3000as upstream instead of localhost. No port exposure needed between containers.
Let’s Encrypt SSL/TLS Configuration
-
What is Let’s Encrypt — Let’s Encrypt is a free, automated certificate authority providing SSL/TLS certificates. Enables HTTPS encryption for websites at no cost. Certificates automatically renew before expiration. Sponsored by Mozilla, EFF, and others to encrypt the web.
-
Automatic Certificate Provisioning — Caddy automatically requests certificates from Let’s Encrypt. No manual certificate purchase or installation needed. Caddy handles domain verification and installation seamlessly. Certificates are stored in
./caddy_data:/datavolume for persistence. -
Caddy Caddyfile Configuration — Simply specify domain names in Caddyfile. Caddy automatically detects HTTPS requirement and requests certificate. No explicit certificate configuration needed.
example.com www.example.com {
reverse_proxy hugo:3000
}
Caddy automatically:
-
Detects domains from Caddyfile
-
Contacts Let’s Encrypt ACME server
-
Verifies domain ownership via HTTP-01 challenge
-
Installs certificate
-
Configures HTTPS on ports 8080/8443
-
Certificate Renewal — Let’s Encrypt certificates valid for 90 days. Caddy automatically renews 30 days before expiration. No downtime during renewal. Renewal happens silently in background without user intervention.
-
HTTPS Redirect — Caddy automatically redirects HTTP traffic to HTTPS. Visitors to
http://example.comare redirected tohttps://example.com. Enforces secure connections by default. Can be disabled withhttp://prefix in Caddyfile if needed. -
Certificate Storage and Persistence — Certificates stored in
./caddy_data:/datamounted volume. Persists across container restarts. If volume is deleted, certificates are re-requested from Let’s Encrypt. Keep backup of./caddy_datadirectory for important deployments. -
Browser Trust and Security — Let’s Encrypt certificates trusted by all modern browsers. Browsers display padlock icon for HTTPS sites. Visitors see green lock indicating secure connection. Increases user trust and site credibility.
-
Rate Limiting Considerations — Let’s Encrypt has rate limits (50 certificates per domain per week). Caddy respects rate limits and handles throttling gracefully. Test configuration before production to avoid exhausting limits. Staging environment recommended for testing.