Skip to content

Deployments

Infrastructure overview

Orchid runs on AWS Elastic Beanstalk (EB). Each agency has its own EB environment connecting to its own MySQL database:

GitHub (master branch)
GitHub Actions
(CI: lint, migration check, tests)
Build artifact
├──► EB Environment: Agency A ──► MySQL: agency_a_db
├──► EB Environment: Agency B ──► MySQL: agency_b_db
└──► EB Environment: Agency C ──► MySQL: agency_c_db
(80+ total environments)

Each EB environment runs the same application code but connects to a different database via environment-specific config.

The Procfile

The Procfile defines what processes EB runs:

web: gunicorn --worker-class gevent --workers 3 --bind 0.0.0.0:$PORT flaskapp:app
  • gunicorn — the production WSGI server
  • gevent — async worker class for handling concurrent requests
  • 3 workers — number of gunicorn workers (configured for EB instance size)
  • flaskapp:app — the Flask app object from flaskapp.py

Config management

Orchid uses a layered config system:

SourceWhereUsed for
flaskapp.cfgCommitted to gitDefaults, non-sensitive config
localflaskapp.cfgLocal only (gitignored)Local dev overrides
EB Environment variablesAWS console / CLIPer-agency secrets and config

In production: There is no localflaskapp.cfg. All sensitive values (DB_HOST, DB_PASS, SENDGRID_API_KEY, SECRET_KEY, etc.) are set as EB environment properties.

To update a production config value: Go to the EB environment in the AWS console → Configuration → Software → Environment properties. The instance restarts automatically after saving.

Migration auto-run on startup

Migrations run automatically when an EB instance starts. The orchid/migrations.py module handles this:

  1. The Flask app starts
  2. migrations.py attempts to acquire a lock in the migration_lock table
  3. If the lock is acquired: run flask db upgrade and release the lock
  4. If the lock is already held: wait and retry (prevents concurrent migration runs when multiple instances start together)

This means deployments do not require manual migration steps. However:

  • New columns added with nullable=True are safe to deploy
  • New tables are safe to deploy
  • New columns with nullable=False and no default require care — the migration will fail on tables with existing rows if not handled correctly

Rolling deployments

EB performs rolling deployments — it updates instances one at a time. During a rolling deployment:

  • Some instances are running the new code; others are still running the old code
  • Both versions connect to the same database
  • This is why only additive migrations are safe — an old instance must be able to read a database that has new columns without crashing

GitHub Actions CI

The CI pipeline runs on every PR:

  1. Migration chain check — verifies single head, valid down_revision chain
  2. Python testspytest with SQLite
  3. JavaScript testsnpm test
  4. Linting — code style checks

A PR cannot merge with failing CI.

.ebextensions/ and .platform/

These directories contain EB-specific configuration:

  • .ebextensions/ — EB config files that run during deployment (e.g., setting up nginx, installing packages)
  • .platform/ — platform hooks for newer EB configurations

If deployment is failing but CI passes, check these files for environment-specific setup that may be broken.

Deployment checklist

Before deploying a change:

  • CI passes (migration check, tests)
  • If adding a column: confirmed it is nullable=True or has a server_default
  • If removing a feature: the removal is backward-compatible (old code that references the column still works)
  • If adding a new environment variable: added it to EB environment properties for all affected agencies before deploying
  • Tested on staging environment before pushing to production agencies