homelab/compose/services/static-sites/README.md
Claude 953a9d52af
feat: Add Caddy static sites and implement domain strategy
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
2025-11-10 12:11:03 +00:00

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 .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)

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

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

See Caddy Templates Docs

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

  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

# 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

  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! 🌐