Architecture Decision Records (ADR)¶
Last Updated: 2025-12-02
Overview¶
This document logs key architectural decisions made for the infrastructure, including context, rationale, trade-offs, and potential migration paths.
ADR-001: Use Docker Compose for Service Orchestration¶
Date: 2024-01-01
Status: Accepted
Context: Need to deploy multiple services with complex interdependencies on a single VM.
Decision¶
Use Docker Compose instead of Kubernetes or Docker Swarm for service orchestration.
Rationale¶
- Single VM deployment doesn't justify Kubernetes complexity
- Docker Compose provides sufficient orchestration for our scale
- Easier to debug and maintain for small teams
- Native support for environment variables and networking
- Simple backup and restore procedures
Trade-offs¶
- Pros: Simple, maintainable, good documentation, fast deployment
- Cons: No built-in HA, limited scaling options, manual updates
Alternatives Considered¶
- Kubernetes: Too complex for single-node deployment
- Docker Swarm: Better for multi-node, adds unnecessary complexity
- Systemd units: Less portable, harder dependency management
Migration Path¶
If scaling beyond single node: 1. Convert compose files to Kubernetes manifests using Kompose 2. Implement external configuration management (Helm/Kustomize) 3. Move to managed Kubernetes service
Lessons Learned¶
- Compose v2 syntax provides all needed features
- Network isolation works well for security
- Volume management requires careful planning
ADR-002: Use Traefik as Reverse Proxy¶
Date: 2024-01-15
Status: Accepted
Context: Need reverse proxy with automatic SSL and service discovery.
Decision¶
Use Traefik v3 instead of Nginx, Caddy, or HAProxy.
Rationale¶
- Native Docker integration with label-based configuration
- Automatic SSL with Let's Encrypt and Cloudflare DNS
- Built-in service discovery
- Good observability with metrics and access logs
- Active development and community
Trade-offs¶
- Pros: Auto-configuration, great Docker support, modern features
- Cons: Learning curve, more complex than Nginx for simple cases
Alternatives Considered¶
- Nginx: Requires manual configuration, no auto-discovery
- Caddy: Simpler but less flexible for complex routing
- HAProxy: Powerful but lacks modern convenience features
Migration Path¶
To migrate away from Traefik: 1. Export routing rules from Traefik API 2. Convert to target proxy configuration 3. Implement service discovery mechanism 4. Test thoroughly with canary deployments
Lessons Learned¶
- Label-based configuration prevents configuration drift
- Middleware chains are powerful for auth/redirects
- DNS challenge works better than HTTP for wildcard certs
ADR-003: Self-Host Supabase¶
Date: 2024-02-01
Status: Accepted
Context: Need PostgreSQL-based backend with auth, storage, and realtime features.
Decision¶
Self-host Supabase instead of using managed service or building custom.
Rationale¶
- Full control over data and infrastructure
- Cost-effective for our scale
- All features available without restrictions
- Can customize and extend as needed
- Good architectural patterns to follow
Trade-offs¶
- Pros: No vendor lock-in, full control, cost savings, customizable
- Cons: Maintenance burden, security responsibility, no SLA
Alternatives Considered¶
- Managed Supabase: Monthly costs, data sovereignty concerns
- Custom stack: Too much development effort
- Firebase: Vendor lock-in, not PostgreSQL
- Hasura + Auth0: More complex, higher cost
Migration Path¶
To managed Supabase: 1. Use Supabase migration tools 2. Update connection strings 3. Verify auth flows work correctly
To custom stack: 1. PostgREST can be replaced with custom API 2. GoTrue can be replaced with other auth 3. Database remains PostgreSQL
Lessons Learned¶
- Bootstrap SQL pattern works well for initialization
- JWT secret management is critical
- Storage API needs careful permission setup
- Backup strategy essential from day one
ADR-004: Use Cloudflare for DNS and SSL¶
Date: 2024-02-15
Status: Accepted
Context: Need reliable DNS with API access for automatic SSL certificates.
Decision¶
Use Cloudflare for DNS management and SSL certificate generation.
Rationale¶
- Free tier sufficient for our needs
- Excellent API for DNS challenges
- DDoS protection included
- DNS analytics helpful for debugging
- Good Traefik integration
Trade-offs¶
- Pros: Free, reliable, good API, security features
- Cons: Dependency on external service, potential API limits
Alternatives Considered¶
- Route53: Costs money, AWS lock-in
- Self-hosted DNS: Complexity, reliability concerns
- HTTP challenges: Doesn't work for wildcard certs
- Manual certs: Operational burden
Migration Path¶
To different DNS provider: 1. Update Traefik configuration for new provider 2. Migrate DNS records 3. Update API credentials 4. Test certificate renewal
Lessons Learned¶
- API token scoping important for security
- Wildcard certificates simplify subdomain management
- DNS challenge more reliable than HTTP
ADR-005: Separate Networks for Security¶
Date: 2024-03-01
Status: Accepted
Context: Need network isolation between external-facing and internal services.
Decision¶
Use separate Docker networks: traefik_proxy for external, supabase_internal for database access, plus cache_redis_network and session_redis_network for Redis services.
Rationale¶
- Clear security boundary
- Prevents accidental exposure
- Easy to audit and understand
- Native Docker feature
- No performance penalty
Trade-offs¶
- Pros: Better security, clear boundaries, easy to implement
- Cons: Services need multiple network attachments, some complexity
Alternatives Considered¶
- Single network: Security risk
- Network policies: Not available in Docker
- Firewall rules: More complex, less portable
- VLANs: Overkill for single host
Migration Path¶
Already using best practice, but if needed: 1. Can add more networks for finer segmentation 2. Can implement service mesh if needed 3. Can add network policies with Kubernetes migration
Lessons Learned¶
- Explicit network membership prevents accidents
- Internal services stay internal
- Cross-network communication needs explicit configuration
ADR-006: Systemd Timers for Scheduled Tasks¶
Date: 2024-03-15
Status: Accepted
Context: Need reliable scheduling for backups and maintenance tasks.
Decision¶
Use systemd timers instead of cron or container-based scheduling.
Rationale¶
- Better logging and monitoring
- Dependency management
- No additional containers needed
- Handles system boot properly
- Built into the OS
Trade-offs¶
- Pros: Reliable, good logs, dependency handling, no overhead
- Cons: Linux-specific, requires systemd knowledge
Alternatives Considered¶
- Cron: Less visibility, basic logging
- Container scheduler: Additional complexity
- n8n/Airflow: Overkill for simple tasks
- Kubernetes CronJobs: Not applicable
Migration Path¶
To container-based scheduling: 1. Convert timer units to CronJob specs 2. Implement job containers 3. Handle logging/monitoring 4. Test failure scenarios
Lessons Learned¶
- OnCalendar syntax intuitive for schedules
- Persistent=true handles missed runs
- Service units provide good isolation
ADR-007: Git for Configuration Management¶
Date: 2024-04-01
Status: Accepted
Context: Need version control for infrastructure configuration.
Decision¶
Use Git repository for all configuration except secrets.
Rationale¶
- Version history for debugging
- Easy rollback capabilities
- Collaboration features
- Can implement GitOps later
- Industry standard practice
Trade-offs¶
- Pros: Version control, collaboration, audit trail, rollback
- Cons: Need to carefully exclude secrets, requires discipline
Alternatives Considered¶
- No version control: Risky, no history
- Config management tool: Overkill for our scale
- Database storage: Less flexible, custom tooling
Migration Path¶
Already using best practice. For GitOps: 1. Implement CI/CD pipeline 2. Add automated testing 3. Implement automatic deployment 4. Add approval workflows
Lessons Learned¶
- .gitignore critical for security
- .env.example files valuable for documentation
- Commit messages should explain why
- Regular commits prevent large disasters
ADR-008: Bootstrap SQL for Database Initialization¶
Date: 2024-04-15
Status: Accepted
Context: Need reproducible database initialization for Supabase.
Decision¶
Use bootstrap SQL files that run on container initialization.
Rationale¶
- Declarative and reproducible
- Version controlled with Git
- No manual steps required
- Can include custom functions/triggers
- Survives container recreation
Trade-offs¶
- Pros: Reproducible, versioned, automated, documented
- Cons: Only runs on first init, migrations need different approach
Alternatives Considered¶
- Manual setup: Error-prone, not reproducible
- Migration tool: Complexity for initial setup
- Backup restore: Includes data, less flexible
Migration Path¶
For schema changes after initialization: 1. Implement migration tool (Flyway, Liquibase) 2. Convert bootstrap SQL to migrations 3. Version all schema changes 4. Test rollback procedures
Lessons Learned¶
- Alphabetical ordering provides control
- Idempotent scripts are better
- Include test data cautiously
- Document each script's purpose
ADR-009: 14-Day Backup Retention¶
Date: 2024-05-01
Status: Accepted
Context: Balance between storage costs and recovery needs.
Decision¶
Implement 14-day retention for automated backups.
Rationale¶
- Two weeks covers most recovery scenarios
- Storage costs remain reasonable
- Allows recovery from weekend issues
- Simple rotation logic
- Can restore before issues discovered
Trade-offs¶
- Pros: Good balance, reasonable cost, simple implementation
- Cons: No long-term archives, might miss slow issues
Alternatives Considered¶
- 7 days: Too short for some scenarios
- 30 days: Higher storage costs
- Incremental forever: Complex management
- No backups: Unacceptable risk
Migration Path¶
To longer retention: 1. Implement tiered storage (daily/weekly/monthly) 2. Use object storage for archives 3. Implement backup testing automation 4. Consider incremental backups
Lessons Learned¶
- Test restores regularly
- Monitor backup sizes
- Alert on backup failures
- Document restore procedures
Decision Template¶
## ADR-XXX: [Decision Title]
**Date**: YYYY-MM-DD
**Status**: Proposed/Accepted/Deprecated/Superseded
**Context**: [Why this decision needed to be made]
### Decision
[What was decided]
### Rationale
[Why this decision was made]
### Trade-offs
- **Pros**: [Positive outcomes]
- **Cons**: [Negative outcomes]
### Alternatives Considered
1. **Option 1**: [Why not chosen]
2. **Option 2**: [Why not chosen]
### Migration Path
[How to move away from this decision if needed]
### Lessons Learned
[What we learned from implementing this]
Review Schedule¶
Architectural decisions should be reviewed: - Quarterly for active development - Annually for stable systems - When major changes are proposed - When issues arise from decisions
Change Process¶
- Propose new ADR with status "Proposed"
- Discuss with team
- Update status to "Accepted" or "Rejected"
- If replacing existing decision, mark old as "Superseded"
- Document lessons learned after implementation