Domain Strategy: - fig.systems: Homelab services only (removed edfig.dev fallback from all services) - edfig.dev: Professional/public sites (personal site, blog) - figgy.foo: Experimental/private content (SSO protected) Removed edfig.dev Fallbacks: - Updated 22 compose files to remove || Host(...edfig.dev) pattern - All homelab services now use fig.systems only - Traefik email remains admin@edfig.dev Added Caddy Static Sites Service: - compose/services/static-sites/ with Caddy 2 - Serves three domains with different configurations: * edfig.dev (personal/professional) - Public, no SSO * blog.edfig.dev (blog) - Public, Markdown rendering, templates * figgy.foo (experimental) - SSO protected, directory browsing - Example sites with modern, responsive designs - Comprehensive README with usage examples - Auto-reload on config changes (no restarts needed) Features: - Markdown rendering (write .md, serves as HTML) - Go templates for dynamic content - Directory browsing (figgy.foo) - Automatic gzip compression - Static asset caching - Zero-downtime config reloads Updated Documentation: - README.md: Added domain strategy section, static sites in directory structure - README.md: Added static sites to service URLs table - README.md: Updated deployment instructions
12 KiB
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
.mdfiles, 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
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
# 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:
# Edit main site
vim sites/edfig.dev/index.html
# Add new page
echo "<h1>About Me</h1>" > 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/:
# 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
<!-- In any .html file under blog.edfig.dev -->
<p>Page generated at: {{.Now.Format "2006-01-02 15:04:05"}}</p>
Example - Include header:
{{include "header.html"}}
<main>
<h1>My Page</h1>
</main>
{{include "footer.html"}}
Template variables:
{{.Now}}- Current time{{.Req.URL}}- Request URL{{.Req.Host}}- Request hostname{{.Req.Method}}- HTTP method{{env "VARIABLE"}}- Environment variable
Directory Browsing (figgy.foo)
figgy.foo has directory browsing enabled:
# 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:
test.figgy.foo {
root * /srv/test.figgy.foo
file_server
encode gzip
}
Add Traefik labels to compose.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:
mkdir -p sites/test.figgy.foo
echo "<h1>Test Site</h1>" > sites/test.figgy.foo/index.html
Reload (instant, no restart):
# 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
example.com {
root * /srv/example
file_server
}
With Compression
example.com {
root * /srv/example
file_server
encode gzip zstd brotli
}
With Caching
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
www.example.com {
redir https://example.com{uri} permanent
}
example.com {
root * /srv/example
file_server
}
With Custom 404
example.com {
root * /srv/example
file_server
handle_errors {
rewrite * /404.html
file_server
}
}
With Basic Auth (alternative to SSO)
example.com {
root * /srv/example
basicauth {
user $2a$14$hashedpassword
}
file_server
}
Generate hashed password:
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:
@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:
encode gzip
- 70-90% size reduction for HTML/CSS/JS
- Faster page loads
- Lower bandwidth usage
Performance Tips
- Optimize images: Use WebP format, compress before uploading
- Minify CSS/JS: Use build tools (optional)
- Use CDN: For high-traffic sites (optional)
- Enable HTTP/2: Traefik handles this automatically
Monitoring
Check Service Status
# Container status
docker ps | grep caddy-static
# Logs
docker logs caddy-static -f
# Resource usage
docker stats caddy-static
Check Specific Site
# 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):
# 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):
{container="caddy-static"}
Filter by status code:
{container="caddy-static"} |= "404"
Troubleshooting
Site not loading
Check container:
docker ps | grep caddy-static
# If not running:
docker compose up -d
Check logs:
docker logs caddy-static
# Look for errors in Caddyfile or file not found
Check DNS:
dig +short edfig.dev
# Should point to your server IP
Check Traefik:
# See if Traefik sees the route
docker logs traefik | grep edfig
404 Not Found
Check file exists:
ls -la sites/edfig.dev/index.html
Check path in Caddyfile:
grep "root" Caddyfile
# Should show: root * /srv/edfig.dev
Check permissions:
# Files should be readable
chmod -R 755 sites/
Changes not appearing
Caddy auto-reloads, but double-check:
# 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:
# 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:
# In Caddyfile for blog.edfig.dev
blog.edfig.dev {
templates # <-- This must be present!
# ...
}
Check file extension:
# Must be .md
mv post.txt post.md
Test rendering:
curl https://blog.edfig.dev/posts/example-post.md
# Should return HTML, not raw markdown
SSO not working on figgy.foo
Check middleware:
# In compose.yaml
traefik.http.routers.figgy-main.middlewares: tinyauth
Check Tinyauth is running:
docker ps | grep tinyauth
Test without SSO:
# Temporarily remove SSO to isolate issue
# Comment out middleware line in compose.yaml
# docker compose up -d
Backup
Backup Site Content
# 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:
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
# Sites should be read-only to Caddy
chmod -R 755 sites/
chown -R $USER:$USER sites/
Resources
Next Steps
- ✅ Deploy Caddy static sites
- ✅ Access edfig.dev, blog.edfig.dev, figgy.foo
- ⬜ Customize edfig.dev with your content
- ⬜ Write first blog post in Markdown
- ⬜ Add experiments to figgy.foo
- ⬜ Set up Git version control for sites
- ⬜ Configure automated backups
Serve static content, simply and securely! 🌐