Skip to content

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:

  1. Optimize for today while ensuring tomorrow's scaling is straightforward
  2. Keep costs predictable without limiting future options
  3. Maintain simplicity that doesn't sacrifice flexibility
  4. 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-ca
  • qa-app-jubiloop-ca
  • app-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 together

Frontend 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:

  1. Developers add variables to env.deploy.yml file
  2. Add secrets to GitHub Secrets once
  3. Scripts auto-generate all needed files
  4. GitHub Actions injects them everywhere
  5. 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:

  1. Push to GitHub
  2. GitHub Actions builds and tests
  3. Ansible deploys to DigitalOcean
  4. 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

  1. Use free tiers while they meet needs
  2. Pay for managed services for critical components
  3. Self-host only when cost savings are significant
  4. 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)

  1. Microservices: Monolith works great for our scale
  2. Kubernetes: Overkill for current needs
  3. Multi-region: Single region is fine for now
  4. CDN for API: Cloudflare handles static assets
  5. Message queues: Direct HTTP calls suffice
  6. GraphQL: REST API is simpler and sufficient

Decision Framework

When evaluating new technology, we ask:

  1. Does it solve a real problem we have today?
  2. Is the complexity worth the benefit?
  3. Can we migrate away from it easily?
  4. Does it fit our budget constraints?
  5. Can our small team manage it?

If not 5/5 "yes", we wait.

Summary

Our infrastructure philosophy is simple:

  1. Start simple, with clear paths to scale
  2. Protect the data, everything else is replaceable
  3. Optimize for developer productivity, not theoretical scale
  4. Pay for what matters, self-host what doesn't
  5. 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.

Built with ❤️ by the Jubiloop team