Common Patterns - Do's and Don'ts¶
Practical examples of what to do and what to avoid.
Adding a New Service¶
❌ Don't: Manual YAML Route¶
# Don't do this
sudo vim /traefik/config/core/my-service-route.yml
cat > /tmp/my-service-route.yml << 'YAML'
http:
routers:
my-service:
rule: "Host(`api.example.com`)"
service: my-service
tls: {certResolver: letsencrypt}
services:
my-service:
loadBalancer:
servers:
- url: http://my-service:8080
YAML
sudo cp /tmp/my-service-route.yml /traefik/config/core/
Problems: - Not tracked in database - Bypasses validation - Manual, not auditable - Won't be regenerated (might get lost) - Other people don't know it exists
✅ Do: Use the API¶
curl -X POST http://localhost:8001/api/domains \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "api.example.com",
"protocol": "http",
"service_type": "api",
"backend_host": "my-service",
"backend_port": 8080,
"environment": "production",
"status": "active"
}'
Benefits: - Tracked in database - Validated by API - Auditable (git log, database history) - Automatically regenerated - Searchable
Fixing a Route Temporarily¶
❌ Don't: Create a "Temporary" Workaround¶
# Don't do this - temporary becomes permanent
sudo vim /traefik/config/core/temp-redirect.yml
# Add emergency redirect
# "I'll remove this in a few days"
# → 6 months later, still there
✅ Do: Use the API with Disabled Status¶
# Need to disable a route temporarily?
curl -X PUT http://localhost:8001/api/domains/42 \
-H "X-API-Key: $API_KEY" \
-d '{"status": "disabled"}'
# Route disappears within 10 seconds
# Everything is tracked
# To re-enable later
curl -X PUT http://localhost:8001/api/domains/42 \
-H "X-API-Key: $API_KEY" \
-d '{"status": "active"}'
Adding a TCP Service¶
❌ Don't: Manual YAML for TCP Route¶
# CharlieHub specific, but principle applies to other systems
sudo vim /traefik/config/core/01-ssh.yml
cat > /tmp/ssh-route.yml << 'YAML'
tcp:
routers:
ssh:
rule: "HostSNI(`ssh.example.com`)"
entryPoints: [ssh]
service: ssh-service
tls: {certResolver: letsencrypt}
services:
ssh-service:
loadBalancer:
servers:
- address: "bastion.internal:22"
YAML
sudo cp /tmp/ssh-route.yml /traefik/config/core/
Problems: - Not tracked in database - Next person doesn't know it's TCP - Can't easily add more TCP services - No constraints on configuration
✅ Do: Use the API with TCP Protocol¶
# CharlieHub example
curl -X POST http://localhost:8001/api/domains \
-H "X-API-Key: $API_KEY" \
-d '{
"domain": "ssh.example.com",
"protocol": "tcp",
"service_type": "tcp-proxy",
"tcp_entrypoint": "ssh",
"backend_host": "bastion.internal",
"backend_port": 22,
"environment": "production",
"status": "active"
}'
Benefits: - Tracked in database - Constraints validate it - Can easily add SSH, PostgreSQL, Redis, etc. - Reusable pattern - Auditable
Fixing a Configuration Problem¶
❌ Don't: Edit the Generated File¶
# Don't do this
vim /traefik/config/generated/routes.yml
# Change something manually
# → On next generation, your change is LOST
# → You wasted time on something that won't stick
✅ Do: Fix at the Source¶
# Step 1: Figure out what's wrong in the database
docker exec -i charliehub-postgres psql -U charliehub -d charliehub_domains \
-c "SELECT domain, backend_host, backend_port FROM domains WHERE domain='api.example.com'"
# Step 2: Fix via the API
curl -X PUT http://localhost:8001/api/domains/42 \
-H "X-API-Key: $API_KEY" \
-d '{"backend_host": "new-backend", "backend_port": 9000}'
# Step 3: Verify it regenerated
curl -s http://localhost:8091/api/http/routers | jq '.[] | select(.name | contains("api-example"))'
# Change is permanent, tracked, auditable
Emergency Access During Downtime¶
❌ Don't: Edit Config Files to "Fix" Things¶
# System is down, API is unreachable
# DON'T do this:
sudo vim /traefik/config/generated/routes.yml
# Manually add a workaround route
# → Your change will be lost when API comes back up
# → You've created confusion about the real state
✅ Do: Use Database Directly (Documented)¶
# If API is down but database is up:
docker exec -i charliehub-postgres psql -U charliehub -d charliehub_domains \
-c "UPDATE domains SET status='active' WHERE domain='critical-service.com'"
# Then:
docker exec charliehub_domain_manager_v3 python3 /app/services/traefik_generator.py
# Changes are made at source (database), everything is consistent
# When API comes back up, it will see the database is already updated
# Document it!
# echo "Emergency fix: updated domains.status via SQL on $(date)" >> /var/log/emergency-fixes.log
Adding a New Feature to the API¶
❌ Don't: Hack Around the Limitation¶
# API doesn't support feature X
# DON'T do this:
# - Manual workaround YAML
# - Direct SQL modifications
# - Edit the generated file
# - Create a script that does the same thing
# → These create technical debt that compounds
✅ Do: Extend the API¶
Discovered a new requirement?
1. Add field to database schema (migration)
2. Update Pydantic models (validation)
3. Update the generator (if needed)
4. Test it works
5. Document it
6. Use the API for that feature
Example: CharlieHub needed TCP routing
→ Added protocol field to database
→ Updated models.py
→ Extended traefik_generator.py
→ Now: curl -X POST /api/domains with protocol='tcp'
→ Reusable for SSH, PostgreSQL, Redis, etc.
Summary¶
| Situation | ❌ Don't | ✅ Do |
|---|---|---|
| Add service | Edit YAML | Use API |
| Disable service | Delete from config | Set status='disabled' |
| Fix config | Edit generated file | Fix in database, regenerate |
| Temporary change | Create workaround file | Use status field |
| Add TCP route | Manual YAML | Use API with protocol='tcp' |
| API limitation | Work around it | Extend the API |
| Emergency | Hack the files | Use database directly |
Key Principle¶
Source → API → Generated
- Source: Database, where config lives
- API: How you interact with it
- Generated: Output that gets consumed
Don't edit the Generated. Don't bypass the API. Fix it at the Source.