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:
| Source | Where | Used for |
|---|---|---|
flaskapp.cfg | Committed to git | Defaults, non-sensitive config |
localflaskapp.cfg | Local only (gitignored) | Local dev overrides |
| EB Environment variables | AWS console / CLI | Per-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:
- The Flask app starts
migrations.pyattempts to acquire a lock in themigration_locktable- If the lock is acquired: run
flask db upgradeand release the lock - 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=Trueare safe to deploy - New tables are safe to deploy
- New columns with
nullable=Falseand 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:
- Migration chain check — verifies single head, valid
down_revisionchain - Python tests —
pytestwith SQLite - JavaScript tests —
npm test - 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=Trueor has aserver_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