| Environment | Host | Branch | App path |
|---|---|---|---|
| Production | 3.142.169.134 |
main |
/data/web/biospex (current release at /data/web/biospex/current) |
| Development | 3.138.217.206 |
development |
/data/web/biospex |
-
SSH: as
ubuntu, using thebiospexaws.pemkey. -
Databases: production and development share one MySQL and MongoDB server,
172.31.20.104, which is private to the VPC. They use separate databases:biospexandbiospex_dev. Check which database you're connected to before running anything destructive. -
Other sites on the production server: it also runs other applications under Supervisor and nginx:
- Digitization Knowledge (CKAN) at
/data/web/digitizationknowledge - Digitization Academy (
daHorizon) rapid.biospex.orgwedigbio-ingest
TODO: who is responsible for each of these.
- Digitization Knowledge (CKAN) at
BIOSPEX deploys with GitHub Actions (.github/workflows/deploy.yml) and Deployer (deploy.php, deploy/custom.php).
| Trigger | Result |
|---|---|
Push or merge to main |
Calculates the next version, deploys to production, and creates a GitHub release. |
Run the workflow manually on development |
Deploys to development. Pushing to development does not deploy, despite what the root README.md says. |
[skip deploy] or [no deploy] in the commit message |
Pushes without deploying. |
[minor]/[feature] or [major]/[breaking] in the commit message |
Bumps the version. See COMMIT_CONVENTIONS.md. |
dep deploy production or dep deploy development |
Deploys manually from a machine that has the key. |
Assets are built in CI and downloaded as an artifact; nothing is built on the server.
- Create an issue, then a branch for it:
gh issue develop N --base development --name Biospex-N --checkout. - Open a PR into
development, merge it, and run the development deploy. - Check the change on development.
- Open a PR from
developmentintomain. Merging it deploys to production.
The task order is in deploy.php:
- Prepare the release. Generate
.envfrom SSM (env:ssm), install Composer dependencies, and download the CI assets. - Set up Laravel. Run
storage:link,package:discover, andfilament:assets. Then runapp:deploy-files --current-path=…, which renders the Supervisor configs intostorage/app/supervisor. - Update the database. Run
migrate, thenapp:update-queries(see below). - Rebuild caches. Clear and rebuild the config, route, view, and event caches, plus
filament:optimize. - Publish. Switch the
currentsymlink. No OPcache reset is needed: nginx passes PHP the real release path ($realpath_root), so each release is new to OPcache. - Restart processes. Run
supervisorctl rereadandsupervisorctl update, thenqueue:restart,reverb:restart, and restart the Panoptes listener if it's running. Then verify the structure.
.env generation lives in the shared AustinMastLab/deployer-recipes Composer package, which every AustinMastLab Deployer site uses:
| Site | Repository | SSM path |
|---|---|---|
| BIOSPEX | AustinMastLab/Biospex | /biospex/<environment> |
| Digitization Academy | AustinMastLab/DigitizationAcademy | /digitizationacademy/<environment> |
| WeDigBio reports | AustinMastLab/wedigbio-reports | /wedigbio-reports/<environment> |
- Deployer 8 required. The package's
env:ssmtask sends its script to the server in a shell heredoc, which Deployer 7 breaks. From v1.0.1 the package refuses to install alongside Deployer 7. - Nothing to install on the servers. The script is sent with each deploy; there is no longer a
~/generate-envin the server's home directory (removed 2026-10-05). Each server only needs the AWS CLI andjq. - Changing it. Edit the package, tag a new version (
v1.0.2, …), then runcomposer update austinmastlab/deployer-recipesin each site and deploy. Test on BIOSPEX or Digitization Academy development first; WeDigBio reports has no development deployment. - First deploy after a change to the output format rewrites
.envonce (values unchanged) and makes one backup; later deploys reportNo changes.
app:update-queries {operation} is an empty dispatcher for one-time data fixes:
- Add the operation to
AppUpdateQueriesCommand. - Set
set('update_queries_operation', '<name>')indeploy.php. - Deploy. The operation runs after the migrations.
- In a follow-up PR, remove the operation and reset the setting to
''.
- Where
.envcomes from. Each server's.envis generated from AWS SSM Parameter Store, at the path/biospex/productionor/biospex/development, by theenv:ssmdeploy task (see Shared deploy tooling;set('ssm_app', 'biospex')indeploy.php). The task runs the package'sgenerate-envscript on the server with the server's IAM role, so secrets never pass through GitHub. The script refuses to write an empty file, writes only after every value has been read, keeps the file's permissions (600on BIOSPEX), and keeps the last 5.env.backup.*files inshared/. - Server IAM roles.
ProdEC2RoleandDevEC2Role(policiesProdEC2DeployPolicyandDevEC2DeployPolicy) allow only what the servers use: SSM read and KMS decrypt for the three sites' parameter paths, the site's S3 bucket and SQS queues, Lambda concurrency (read and set), and starting the zip Step Function. Lambda code, alias, and trigger management are done from a developer machine asLocalDeployUser, not from the servers. - Rolling back.
dep rollback <environment>only switches thecurrentsymlink; it doesn't regenerate.env. If a rollback needs an older.env, copy the matchingshared/.env.backup.*back overshared/.env. - Pushing a local file to SSM:
vendor/bin/push-env-params biospex <environment>pushes each line of.env.aws.<environment>as aSecureStringparameter. Those files hold real values, and git ignores them through/.env.*.vendor/bin/remove-env-params biospex <environment>deletes every parameter under/biospex/<environment>, after you type the environment name to confirm; it can't be undone. - Supervisor values. Some variables (
APP_SERVER_USER,APP_TAG,PANOPTES_LISTENER_ENABLED, the queue names, and others) are written into the Supervisor configs byapp:deploy-files. After changing one of them, runapp:deploy-files, thensupervisorctl rereadandsupervisorctl update. env.examplelists the settings SSM sets, with local placeholder values, plus a few local-only keys (AWS keys, cache and session drivers). When you add a setting to SSM, add it there too.
| Task | Command |
|---|---|
| Status of all programs | sudo supervisorctl status |
| Restart the Pusher listener | sudo supervisorctl restart biospex:panoptes-pusher |
| Restart queue workers (after code changes) | php artisan queue:restart |
| Start or stop an SQS listener | php artisan sqs:control <queue-key> --action=start (or stop) |
| Count jobs in a Beanstalkd queue | php artisan queue:count |
| Stop or start a Lambda in every environment | php artisan app:lambda-control <function> stop (or start) |
| Stop or start one environment's Lambda triggers | php artisan app:lambda-control <function> stop --alias=dev (run from your machine) |
| Purge the AWS queues for an environment | php artisan app:awsqueue-purge |
- Laravel logs:
/data/web/biospex/shared/storage/logs. These are truncated on each deploy. - Supervisor program logs:
/var/log/supervisor/biospex/. Supervisor rotates them itself, at 50 MB with 3 backups. Logrotate doesn't manage them. - Stopped programs: the
prod-*SQS listener programs normally show as STOPPED. They start when needed and stop after an idle period.
- Environment: WSL Ubuntu 22, with the app at
/data/web/biospexand the local domainbiospex.test. The API is atapi.biospex.test. - Services needed: PHP 8.5, MySQL, MongoDB, Beanstalkd, Redis, and Supervisor. The local Supervisor configs come from the same templates, with the
loc-prefix. - Assets:
yarn install, thenyarn build(oryarn devfor the Vite dev server). SetVITE_REVERB_*in.envso the build connects to the local Reverb. - Tests:
php artisan test --compact. Tests use Pest on SQLite, with the sync queue and the array cache. MongoDB isn't available, so code that depends on MongoDB must be faked or kept out of tests. - Formatting:
vendor/bin/pint --dirty. - Project guidelines: these are in
.ai/guidelinesand.ai/rules. Laravel Boost compiles them intoCLAUDE.mdandAGENTS.md.
- Shared database server. Development work can affect production performance, and a mistaken connection string can point at production data.
- Duplicate transcriptions. The live Pusher feed, job retries, and the nightly backfill can all write the same classification. Unique indexes on
pusher_transcriptions.classification_idandevent_transcriptionsprevent duplicates, so keep them in place. - MongoDB indexes. MongoDB can't hold two indexes on the same key with different options. To change one, drop the old index first. Restoring a database dump can bring old indexes back.
- Large
distinctqueries hit MongoDB's 16 MB limit. Use an aggregation ($groupwith$count) instead. - Pusher quota. If Pusher reports error 4004 (over quota), the listener stops reconnecting for an hour.
- Upgrade packages that extend framework commands together with Laravel. On 2026-10-05, Digitization Academy's Horizon workers crashed after a Laravel update because
horizon:workextendsqueue:work, Laravel added a new option toqueue:work, and Horizon was pinned to an old version (5.35.*) that didn't declare it. After acomposer update, checkcomposer outdated --directfor packages held back by tight constraints, and after deploying check that queue workers stay up, not just that Supervisor shows RUNNING. - Disable the
devandlocLambda SQS triggers after testing. Enabled triggers poll SQS constantly and use up the monthly AWS free tier quickly. See Lambda functions. - SQS listeners are started by jobs. If an export, OCR, or reconcile run stalls, check whether its listener is running and whether its Lambda concurrency is set to 0.