Appearance
Infrastructure Philosophy & Technology Decisions
Core Principle: Pragmatism With Future-Ready Architecture
Jubiloop's infrastructure embodies pragmatic decision-making with deliberate growth paths. Every choice is made to:
- Optimize for today while ensuring tomorrow's scaling is straightforward
- Keep costs predictable without limiting future options
- Maintain simplicity that doesn't sacrifice flexibility
- Protect what matters (data) while staying lean elsewhere
Key Technology Decisions
Frontend Hosting: Why Cloudflare
Operational Fit:
- The React SPA can deploy as static assets on Cloudflare Pages
- Terraform provisions the marketing Pages projects and domains; OpenNext deploys the Next.js Worker runtime and static assets with the matching project name
- Separate projects and Workers keep development, QA, and production deployments independent
- DNS, TLS, edge delivery, and application hosting remain in one provider
Key Advantage: Cloudflare supports isolated frontend deployments for each environment. The webapp uses Pages projects:
dev-app-jubiloop-caqa-app-jubiloop-caapp-jubiloop-ca
Marketing uses separate OpenNext Worker deployments for development, QA, and production.
Decision: Cloudflare Pages serves the React webapp, while Cloudflare Workers supports the OpenNext marketing application. Both use Cloudflare's network and preserve environment isolation.
Backend Hosting: Why DigitalOcean
Current Setup:
- One shared dev/QA droplet and one dedicated production API droplet
- Droplet sizes and prices are configuration inputs and can change without a documentation update
- Scaling path: resize droplets, add instances when needed, or move providers when operations justify it
Why Not Hetzner?
- Considered Hetzner (cheaper VPS option)
- Chose DigitalOcean for:
- Superior developer experience
- Extensive documentation
- Managed services (databases, spaces, etc.)
- Better community support
Comparison:
- AWS: Enterprise-grade but complex pricing, steep learning curve
- DigitalOcean: Predictable pricing, excellent developer experience
- Future: Can move to AWS when we need its advanced services
Production Database: Why DigitalOcean Managed PostgreSQL
The Logic:
- Database = critical data = must not fail
- Backups, failover, scaling are complex — we pay for someone else to handle it
- DigitalOcean Managed PostgreSQL: automatic backups, PgBouncer connection pooling, firewall rules built-in
- Co-located with our backend droplet (both in
tor1) = minimal latency - Scaling path: resize cluster tier → add read replicas → Enterprise
Development and QA remain separate self-hosted PostgreSQL containers on their shared droplet. Their droplet backups are disabled, so this managed-service rationale and its recovery guarantees apply only to production.
Key Insight: "Never self-host what you can't afford to lose"
Backend Framework: Why AdonisJS
Why AdonisJS:
- Batteries included: Auth, ORM, validation, migrations out of the box
- Laravel-like: Proven patterns, great DX
- TypeScript first: Not bolted on
- Integrated: No need to wire 20 packages together
Code Impact:
typescript
// This is all built-in, no setup needed:
@column()
public email: string
@hasMany(() => Event)
public events: HasMany<typeof Event>
// vs Express: install 20 packages, wire them up, hope they work togetherFrontend Framework: Why React + TanStack
React + Vite:
- Team knows it well
- Massive ecosystem
- TanStack Router for type-safe routing
- Zustand for simple state management
Why Not Next.js for Web App:
- We don't need SSR for an authenticated app
- Adds complexity without benefits for our use case
- Marketing site uses Next.js where SEO matters
Infrastructure as Code: Why Not Kubernetes?
Current Reality:
- Team of 2 developers
- No dedicated DevOps engineer
- ~100-1000 initial users expected
Operational Tradeoff:
- A managed control plane and worker nodes add resources before they add product value
- Kubernetes requires operational expertise the current team does not need for this topology
- Current pricing belongs in the Environment Reference
Our Choice: Docker + docker compose
- Runs on the infrastructure already required by the API
- Deployment: Simple, debuggable, rollback-friendly
- Future path: Can migrate to Kubernetes when justified
Infrastructure Evolution Path
Our infrastructure is designed to evolve with clear triggers:
Current
- Stack: Two configured DigitalOcean droplets, DigitalOcean Managed PostgreSQL, and Cloudflare
- Monitoring: Basic health checks (planned: Sentry for error tracking)
Growth
- Trigger: API response time >500ms consistently
- Changes:
- Larger droplet (vertical scaling)
- Upgrade PostgreSQL cluster tier for better performance
- Move Redis from per-droplet containers to a dedicated or managed service when measured load or availability requirements justify it
- Implement Sentry for error tracking
Scale
- Trigger: Vertical scaling limits reached
- Changes:
- Multiple droplets (horizontal scaling)
- Load balancer
- Dedicated Redis instance
- Consider managed services
Enterprise
- Trigger: Worth dedicated DevOps engineer
- Changes:
- Migrate to AWS/Kubernetes
- Multi-region deployment
- Advanced monitoring
Configuration Philosophy
The env.deploy.yml Pattern
Problem: Environment variables scattered everywhere Solution: Single source of truth
yaml
# env.deploy.yml
DEV:
server:
DATABASE_URL: from_secrets # Becomes DEV_SERVER_DATABASE_URL
REDIS_URL: 'redis://localhost:6379'
webapp:
API_URL: 'https://dev-api.jubiloop.ca'Benefits:
- Developers add variables to env.deploy.yml file
- Add secrets to GitHub Secrets once
- Scripts auto-generate all needed files
- GitHub Actions injects them everywhere
- No manual Cloudflare/DigitalOcean console work
Conventions Over Configuration
Naming Conventions:
- Service names match everywhere (docker-compose, env.deploy.yml)
- Environment prefixes are predictable (DEV*, QA*, PROD_)
- File locations follow patterns
Development Philosophy
Local Development = Production-Like
Principle: If it works locally, it should work in production
Implementation:
- Docker Compose locally mirrors production
- The same relational model and migrations across supported PostgreSQL versions
- Same Redis setup
- Same environment variable patterns
Deployment Simplicity
Current Process:
- Push to GitHub
- GitHub Actions builds and tests
- Ansible deploys to DigitalOcean
- Docker Compose recreates the selected containers and health checks verify the result
No:
- Complex orchestration
- Service meshes
- Multi-stage builds (yet)
- Helm charts
Yes:
- Simple, understandable process
- Git reverts and explicit redeployment when recovery is needed
- Clear logs
- Fast iteration
Monitoring & Observability Strategy
Current State
- Application logs via Docker
- Deployment-time health checks
- Manual error checking
Planned Additions
- Sentry (planned): Error tracking and performance monitoring
- Easy integration with both frontend and backend
- Real user monitoring
- Future: OpenTelemetry when complexity justifies it
Security Principles
Data Security First
- Production managed database: Provider-managed encryption and backups
- HTTPS everywhere: Via Cloudflare
- Secrets in GitHub: Never in code
- Session-based auth: HTTP-only cookies, not JWT
Simple Security
- No: Complex IAM policies, VPNs, bastion hosts
- Yes: SSH keys, firewall rules, regular updates
Cost Management
Current resource sizes and estimated costs are maintained in the Environment Reference, where they can be read alongside the Terraform variables that control them.
Cost Scaling Principles
- Use free tiers while they meet needs
- Pay for managed services for critical components
- Self-host only when cost savings are significant
- Monitor usage to predict scaling needs
Technology Integration Benefits
Single Dashboard Approach
- Cloudflare: DNS + Frontend hosting + SSL
- DigitalOcean: Backend + Redis + Managed PostgreSQL
- GitHub: Code + CI/CD + Secrets
Migration-Ready Architecture
- Containerized: Apps portable to any cloud
- Terraform: Infrastructure reproducible anywhere
- Standard tools: PostgreSQL, Redis, Docker
- No vendor lock-in: Can switch any component
Future Considerations
When to Migrate Components
Database → Larger Tier:
- Storage approaching cluster capacity
- Need read replicas for query performance
- Require connection pool expansion
Hosting → AWS:
- Need auto-scaling
- Require multiple regions
- Have dedicated DevOps resources
Monitoring → Advanced:
- Debugging becomes time-consuming
- Need distributed tracing
- Require custom metrics
What We're NOT Doing (Yet)
- Microservices: Monolith works great for our scale
- Kubernetes: Overkill for current needs
- Multi-region: Single region is fine for now
- CDN for API: Cloudflare handles static assets
- Message queues: Direct HTTP calls suffice
- GraphQL: REST API is simpler and sufficient
Decision Framework
When evaluating new technology, we ask:
- Does it solve a real problem we have today?
- Is the complexity worth the benefit?
- Can we migrate away from it easily?
- Does it fit our budget constraints?
- Can our small team manage it?
If not 5/5 "yes", we wait.
Summary
Our infrastructure philosophy is simple:
- Start simple, with clear paths to scale
- Protect the data, everything else is replaceable
- Optimize for developer productivity, not theoretical scale
- Pay for what matters, self-host what doesn't
- Monitor costs and performance, scale when data justifies it
This approach lets us focus on building features while maintaining a robust, scalable foundation that can grow with our success.