Skip to content

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

  1. Kubernetes: Too complex for single-node deployment
  2. Docker Swarm: Better for multi-node, adds unnecessary complexity
  3. 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

  1. Nginx: Requires manual configuration, no auto-discovery
  2. Caddy: Simpler but less flexible for complex routing
  3. 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

  1. Managed Supabase: Monthly costs, data sovereignty concerns
  2. Custom stack: Too much development effort
  3. Firebase: Vendor lock-in, not PostgreSQL
  4. 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

  1. Route53: Costs money, AWS lock-in
  2. Self-hosted DNS: Complexity, reliability concerns
  3. HTTP challenges: Doesn't work for wildcard certs
  4. 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

  1. Single network: Security risk
  2. Network policies: Not available in Docker
  3. Firewall rules: More complex, less portable
  4. 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

  1. Cron: Less visibility, basic logging
  2. Container scheduler: Additional complexity
  3. n8n/Airflow: Overkill for simple tasks
  4. 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

  1. No version control: Risky, no history
  2. Config management tool: Overkill for our scale
  3. 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

  1. Manual setup: Error-prone, not reproducible
  2. Migration tool: Complexity for initial setup
  3. 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

  1. 7 days: Too short for some scenarios
  2. 30 days: Higher storage costs
  3. Incremental forever: Complex management
  4. 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

  1. Propose new ADR with status "Proposed"
  2. Discuss with team
  3. Update status to "Accepted" or "Rejected"
  4. If replacing existing decision, mark old as "Superseded"
  5. Document lessons learned after implementation