# Caddy Static Sites Server Serves static websites for edfig.dev (professional), blog.edfig.dev (blog), and figgy.foo (experimental). ## Overview **Caddy** is a modern web server with automatic HTTPS and simple configuration: - ✅ **Static file serving** - HTML, CSS, JavaScript, images - ✅ **Markdown rendering** - Write `.md` files, served as HTML automatically - ✅ **Templates** - Dynamic content with Go templates - ✅ **Directory browsing** - Beautiful file listing (figgy.foo) - ✅ **Auto-compression** - Gzip for all responses - ✅ **Zero-downtime reloads** - Config changes apply instantly ## Domain Strategy ### edfig.dev (Professional/Public) - **Purpose**: Personal website, portfolio - **URL**: https://edfig.dev or https://www.edfig.dev - **SSO**: No (public site) - **Content**: `/sites/edfig.dev/` ### blog.edfig.dev (Blog/Public) - **Purpose**: Technical blog, articles - **URL**: https://blog.edfig.dev - **SSO**: No (public blog) - **Content**: `/sites/blog.edfig.dev/` - **Features**: Markdown auto-rendering, templates ### figgy.foo (Experimental/Private) - **Purpose**: Testing, development, experiments - **URL**: https://figgy.foo or https://www.figgy.foo - **SSO**: Yes (protected by Tinyauth) - **Content**: `/sites/figgy.foo/` - **Features**: Directory browsing, templates ## Quick Start ### 1. Deploy ```bash cd ~/homelab/compose/services/static-sites docker compose up -d ``` ### 2. Access Sites - **edfig.dev**: https://edfig.dev - **Blog**: https://blog.edfig.dev - **Experimental**: https://figgy.foo (requires SSO login) ### 3. Verify ```bash # Check container is running docker ps | grep caddy-static # Check logs docker logs caddy-static # Test sites curl -I https://edfig.dev curl -I https://blog.edfig.dev ``` ## Directory Structure ``` static-sites/ ├── compose.yaml # Docker Compose + Traefik labels ├── Caddyfile # Caddy configuration ├── .env # Environment variables ├── .gitignore # Ignored files ├── README.md # This file └── sites/ # Site content (can be version controlled) ├── edfig.dev/ │ ├── index.html │ ├── assets/ │ │ ├── css/ │ │ ├── js/ │ │ └── images/ │ └── ... ├── blog.edfig.dev/ │ ├── index.html │ └── posts/ │ ├── example-post.md # Markdown posts │ └── ... └── figgy.foo/ ├── index.html └── experiments/ └── ... ``` ## Managing Content ### Adding/Editing HTML Simply edit files in the `sites/` directory: ```bash # Edit main site vim sites/edfig.dev/index.html # Add new page echo "

About Me

" > sites/edfig.dev/about.html # Changes are live immediately (no restart needed!) ``` ### Writing Blog Posts (Markdown) Create `.md` files in `sites/blog.edfig.dev/posts/`: ```bash # Create new post cat > sites/blog.edfig.dev/posts/my-post.md << 'EOF' # My New Blog Post **Published:** January 10, 2025 This is my blog post content... ## Code Example ```bash docker compose up -d ``` [Back to Blog](/) EOF # Access at: https://blog.edfig.dev/posts/my-post.md # (renders as HTML automatically!) ``` **Markdown features:** - Headers (`#`, `##`, `###`) - **Bold**, *italic*, `code` - Links, images - Lists (ordered/unordered) - Code blocks with syntax highlighting - Tables - Blockquotes ### Using Templates Caddy supports Go templates for dynamic content: **Example - Current time:** ```html

Page generated at: {{.Now.Format "2006-01-02 15:04:05"}}

``` **Example - Include header:** ```html {{include "header.html"}}

My Page

{{include "footer.html"}} ``` **Template variables:** - `{{.Now}}` - Current time - `{{.Req.URL}}` - Request URL - `{{.Req.Host}}` - Request hostname - `{{.Req.Method}}` - HTTP method - `{{env "VARIABLE"}}` - Environment variable See [Caddy Templates Docs](https://caddyserver.com/docs/caddyfile/directives/templates) ### Directory Browsing (figgy.foo) figgy.foo has directory browsing enabled: ```bash # Add files to browse cp some-file.txt sites/figgy.foo/experiments/ # Access: https://figgy.foo/experiments/ # Shows beautiful file listing with search! ``` ## Adding New Sites ### Option 1: New Subdomain (same domain) **Add to Caddyfile:** ```caddy test.figgy.foo { root * /srv/test.figgy.foo file_server encode gzip } ``` **Add Traefik labels to compose.yaml:** ```yaml # test.figgy.foo traefik.http.routers.figgy-test.rule: Host(`test.figgy.foo`) traefik.http.routers.figgy-test.entrypoints: websecure traefik.http.routers.figgy-test.tls.certresolver: letsencrypt traefik.http.routers.figgy-test.service: caddy-static traefik.http.routers.figgy-test.middlewares: tinyauth # If SSO needed ``` **Create site directory:** ```bash mkdir -p sites/test.figgy.foo echo "

Test Site

" > sites/test.figgy.foo/index.html ``` **Reload (instant, no restart):** ```bash # Caddy auto-reloads when Caddyfile changes! # Just wait 1-2 seconds, then access https://test.figgy.foo ``` ### Option 2: New Domain Follow same process but use new domain name. Make sure DNS points to your server. ## Caddyfile Features ### Basic Site ```caddy example.com { root * /srv/example file_server } ``` ### With Compression ```caddy example.com { root * /srv/example file_server encode gzip zstd brotli } ``` ### With Caching ```caddy example.com { root * /srv/example file_server @static { path *.css *.js *.jpg *.png *.gif *.ico } header @static Cache-Control "public, max-age=604800" } ``` ### With Redirects ```caddy www.example.com { redir https://example.com{uri} permanent } example.com { root * /srv/example file_server } ``` ### With Custom 404 ```caddy example.com { root * /srv/example file_server handle_errors { rewrite * /404.html file_server } } ``` ### With Basic Auth (alternative to SSO) ```caddy example.com { root * /srv/example basicauth { user $2a$14$hashedpassword } file_server } ``` Generate hashed password: ```bash docker exec caddy-static caddy hash-password --plaintext "mypassword" ``` ## Traefik Integration All sites route through Traefik: ``` Internet → DNS (*.edfig.dev, *.figgy.foo) ↓ Traefik (SSL termination) ↓ Tinyauth (SSO check for figgy.foo only) ↓ Caddy (static file serving) ``` **SSL certificates:** - Traefik handles Let's Encrypt - Caddy receives plain HTTP on port 80 - Users see HTTPS **SSO protection:** - `edfig.dev` & `blog.edfig.dev`: No SSO (public) - `figgy.foo`: SSO protected (private) ## Performance ### Caching Static assets automatically cached: ```caddy @static { path *.css *.js *.jpg *.jpeg *.png *.gif *.ico *.svg } header @static Cache-Control "public, max-age=604800, immutable" ``` - 7 days cache for images, CSS, JS - Browsers won't re-request until expired ### Compression All responses auto-compressed with gzip: ```caddy encode gzip ``` - 70-90% size reduction for HTML/CSS/JS - Faster page loads - Lower bandwidth usage ### Performance Tips 1. **Optimize images**: Use WebP format, compress before uploading 2. **Minify CSS/JS**: Use build tools (optional) 3. **Use CDN**: For high-traffic sites (optional) 4. **Enable HTTP/2**: Traefik handles this automatically ## Monitoring ### Check Service Status ```bash # Container status docker ps | grep caddy-static # Logs docker logs caddy-static -f # Resource usage docker stats caddy-static ``` ### Check Specific Site ```bash # Test site is reachable curl -I https://edfig.dev # Test with timing curl -w "@curl-format.txt" -o /dev/null -s https://edfig.dev # Check SSL certificate echo | openssl s_client -connect edfig.dev:443 -servername edfig.dev 2>/dev/null | openssl x509 -noout -dates ``` ### Access Logs Caddy logs to stdout (captured by Docker): ```bash # View logs docker logs caddy-static # Follow logs docker logs caddy-static -f # Last 100 lines docker logs caddy-static --tail 100 ``` ### Grafana Logs All logs forwarded to Loki automatically: **Query in Grafana** (https://logs.fig.systems): ```logql {container="caddy-static"} ``` Filter by status code: ```logql {container="caddy-static"} |= "404" ``` ## Troubleshooting ### Site not loading **Check container:** ```bash docker ps | grep caddy-static # If not running: docker compose up -d ``` **Check logs:** ```bash docker logs caddy-static # Look for errors in Caddyfile or file not found ``` **Check DNS:** ```bash dig +short edfig.dev # Should point to your server IP ``` **Check Traefik:** ```bash # See if Traefik sees the route docker logs traefik | grep edfig ``` ### 404 Not Found **Check file exists:** ```bash ls -la sites/edfig.dev/index.html ``` **Check path in Caddyfile:** ```bash grep "root" Caddyfile # Should show: root * /srv/edfig.dev ``` **Check permissions:** ```bash # Files should be readable chmod -R 755 sites/ ``` ### Changes not appearing **Caddy auto-reloads**, but double-check: ```bash # Check file modification time ls -lh sites/edfig.dev/index.html # Force reload (shouldn't be needed) docker exec caddy-static caddy reload --config /etc/caddy/Caddyfile ``` **Browser cache:** ```bash # Force refresh in browser: Ctrl+Shift+R (Linux/Win) or Cmd+Shift+R (Mac) # Or open in incognito/private window ``` ### Markdown not rendering **Check templates enabled:** ```caddy # In Caddyfile for blog.edfig.dev blog.edfig.dev { templates # <-- This must be present! # ... } ``` **Check file extension:** ```bash # Must be .md mv post.txt post.md ``` **Test rendering:** ```bash curl https://blog.edfig.dev/posts/example-post.md # Should return HTML, not raw markdown ``` ### SSO not working on figgy.foo **Check middleware:** ```yaml # In compose.yaml traefik.http.routers.figgy-main.middlewares: tinyauth ``` **Check Tinyauth is running:** ```bash docker ps | grep tinyauth ``` **Test without SSO:** ```bash # Temporarily remove SSO to isolate issue # Comment out middleware line in compose.yaml # docker compose up -d ``` ## Backup ### Backup Site Content ```bash # Backup all sites cd ~/homelab/compose/services/static-sites tar czf sites-backup-$(date +%Y%m%d).tar.gz sites/ # Backup to external storage scp sites-backup-*.tar.gz user@backup-server:/backups/ ``` ### Version Control (Optional) Consider using Git for your sites: ```bash cd sites/ git init git add . git commit -m "Initial site content" # Add remote git remote add origin git@github.com:efigueroa/sites.git git push -u origin main ``` ## Security ### Public vs Private **Public sites** (`edfig.dev`, `blog.edfig.dev`): - No SSO middleware - Accessible to everyone - Use for portfolio, blog, public content **Private sites** (`figgy.foo`): - SSO middleware enabled - Requires LLDAP authentication - Use for experiments, private content ### Content Security **Don't commit:** - API keys - Passwords - Private information - Sensitive data **Do commit:** - HTML, CSS, JS - Images, assets - Markdown blog posts - Public content ### File Permissions ```bash # Sites should be read-only to Caddy chmod -R 755 sites/ chown -R $USER:$USER sites/ ``` ## Resources - [Caddy Documentation](https://caddyserver.com/docs/) - [Caddyfile Tutorial](https://caddyserver.com/docs/caddyfile-tutorial) - [Templates Documentation](https://caddyserver.com/docs/caddyfile/directives/templates) - [Markdown Rendering](https://caddyserver.com/docs/caddyfile/directives/templates#markdown) ## Next Steps 1. ✅ Deploy Caddy static sites 2. ✅ Access edfig.dev, blog.edfig.dev, figgy.foo 3. ⬜ Customize edfig.dev with your content 4. ⬜ Write first blog post in Markdown 5. ⬜ Add experiments to figgy.foo 6. ⬜ Set up Git version control for sites 7. ⬜ Configure automated backups --- **Serve static content, simply and securely!** 🌐