Docker Installation
8 minute read
The easiest way to install and run OpenDataBio is using Docker and the docker configuration files provided, which contain the required configuration to run OpenDataBio. It uses nginx, MySQL, and Supervisor for queues
Choose a profile
OpenDataBio provides two Compose profiles:
docker-compose.yml: development and local testing, with source bind mounts, phpMyAdmin, and ports8081/8082.docker-compose.prod.yml: production-oriented, immutable application images, no source bind mounts or phpMyAdmin, a dedicated database user, healthchecks, and named volumes.
The Makefile does not ask which profile you want. The target selects it:
make docker-initstarts the development profile and uses.env;make init-prodstarts the production profile and uses.env.production.
For a production installation, or a production-profile test alongside an existing Apache installation, use make init-prod.
.env. Production Docker uses its own .env.production file and requires DB_HOST=mysql and REDIS_HOST=redis.If you accidentally start make docker-init, interrupt it with Ctrl+C and stop only the development Compose project:
docker compose -p odb down
Do not add -v, because that option deletes the selected Docker project’s volumes.
Production installation
1. Prepare the environment
cd opendatabio
cp .env.production.example .env.production
nano .env.production
chmod 600 .env.production
Compose reads .env.production on the host and injects its values into the
application containers. The production image intentionally does not contain a
/var/www/html/.env file; Laravel reads the injected environment variables.
At minimum, replace:
APP_URL=https://data.example.org
ASSET_URL=https://data.example.org
APP_FORCE_HTTPS=true
APP_HTTP_PORT=80
DB_DATABASE=opendatabio
DB_USERNAME=opendatabio
DB_PASSWORD=a-strong-application-password
DB_ROOT_PASSWORD=a-different-strong-root-password
If TLS terminates in an external reverse proxy, keep the application containers on a private HTTP network/port and configure the proxy to forward the original host and protocol.
For a local production-profile test while Apache already uses port 80:
APP_URL=http://localhost:8083
ASSET_URL=http://localhost:8083
APP_FORCE_HTTPS=false
APP_HTTP_PORT=8083
2. Build and initialize
The initialization script:
- generates
APP_KEYonly when it is empty; - builds the frontend once and copies the same generated assets into the self-contained PHP and nginx images;
- waits for MySQL and Redis healthchecks;
- runs migrations;
- configures interface and user-content locales;
- caches Laravel configuration/routes/views;
- starts nginx and queue workers;
- runs the locale audit.
make init-prod
To initialize a new production database and then optionally import the version-compatible reference data for locations and taxons:
make init-prod SEED=1
The seed step is interactive and requires typing PROCEED. It replaces the
current location and taxon reference tables, so use it only for a new
installation or when the version-specific upgrade notes explicitly instruct
you to replace them. To run it later against an initialized production
installation:
make seed-prod
The production seed runs entirely inside the odb-prod containers and does not
read, modify, or remove files from an Apache installation’s local storage/.
The default locale selection is:
interface: en,es,pt-br
user-entered content: pt-br
Override it for the initialization command when required:
ODB_INTERFACE_LOCALES=en,es,pt-br \
ODB_CONTENT_LOCALES=pt-br,en \
make init-prod
ODB_CONTENT_LOCALES initializes the content locale selection. The primary
locale is always enabled and essential translated fields remain required in
that locale; translations in other enabled locales are optional.
Assisted translation is disabled by default. To enable the only currently
supported provider, configure Google Cloud Translation v3 before
make init-prod. Google currently applies a monthly free usage credit to the
first 500,000 NMT characters, but billing is required and excess usage is
charged. Configure API quotas and billing alerts and verify current pricing.
mkdir -p docker-secrets
cp /secure/source/google-translation.json docker-secrets/
chmod 700 docker-secrets
chmod 600 docker-secrets/google-translation.json
USER_TRANSLATION_PROVIDER=google
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_TRANSLATION_LOCATION=global
GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/google-translation.json
The ignored docker-secrets directory is mounted read-only at /run/secrets
inside both Laravel and queue containers. Never add its files to the image or
repository.
Test the selected provider:
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan translations:check --source=en --target=es
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan translations:check --source=en --target=es --live
Do not regenerate APP_KEY after data has been stored.
3. Validate production
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml ps
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T redis redis-cli ping
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan migrate:status
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan locales:audit
curl -I "${APP_URL:-http://localhost:8083}/"
Inspect logs:
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml logs --tail=200 nginx
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml logs --tail=200 laravel
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml logs --tail=200 supervisord
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml logs --tail=200 mysql
CSP is initially sent as Content-Security-Policy-Report-Only. Test the complete UI and review browser reports before enforcing it in docker/prod/nginx.conf.
Development quick start
This section is only for a development checkout. Do not follow it in the same checkout that currently serves an Apache installation: the development profile uses .env, bind-mounts the source tree, and may write to local application directories.
Prerequisites:
- Docker with the Compose v2 plugin (
docker compose). - Linux/macOS: a user allowed to access the Docker socket, or use a rootless Docker installation.
- Windows: Docker Desktop with WSL2/Hyper-V.
makefor the short commands below.- Node.js 22 and npm on the host. The development source bind mount replaces
the image’s application tree, so
make docker-initbuilds the unversioned frontend and Livewire assets in the checkout.
Preserve any non-Docker environment file first:
cp .env .env.backup.apache
cp .env.docker .env
make docker-init
For a new development database, import the optional location and taxon reference data after initialization:
make seed-odb
Alternatively, run both steps with make docker-init SEED=1. The seed replaces
the current location and taxon reference tables and asks for explicit
confirmation.
The development application is available at http://localhost:8081 and phpMyAdmin at http://localhost:8082.
Default login:
user: admin@example.org
password: password1
Change the password after installation.
The initialization command is for a new installation. It does not overwrite an existing APP_KEY, but migrations and seed operations must still be treated as database changes.
Make commands
Build and database
make docker-init- copy.env.dockerif.envis absent, build/start containers, install dependencies, generate a missing key, migrate, and create the storage linkmake build- build containersmake key-generate- generate an app key only if one does not already existmake composer-install- install PHP dependenciesmake composer-update- update php dependenciesmake composer-dump-autoload- execute composer dump-autoload within containermake migrate- create or update the databasemake drop-migrate- delete and recreate the databasemake seed-odb- seed the database with locations and taxonsmake seed-prod- seed a production Docker database without touching host storagemake init-prod- build and initialize the production profilemake start-prod/make stop-prod- start or stop the production profile
Container access
make start- start all containersmake stop- stop all containersmake restart- restart all containersmake ssh- enter the main laravel app containermake ssh-mysql- enter the mysql container, so you may the log to the database usingmysql -uUSER -pPWDmake mysql- enter the docker mysql consolemake ssh-nginx- enter the nginx containermake ssh-supervisord- enter the supervisord container
Maintenance commands
make optimize- clean caches and log filesmake info- show app infomake logs- show laravel logsmake logs-mysql- show mysql logsmake logs-nginx- show nginx logsmake logs-supervisord- show supervisor logs
Data persistence and clean resets
MySQL, Redis, and production media use named volumes. Rebuilding an image does not delete these volumes.
docker volume ls
To reset only a development test project, including its database:
docker compose -p odb down -v --remove-orphans
For the production profile:
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml down -v --remove-orphans
-v option permanently deletes the database, Redis, and production storage volumes for that Compose project. Back them up first. Do not use docker system prune -a as an application reset command; it is not scoped to OpenDataBio.Updating an existing Docker installation
Before updating, back up your database and storage/app/public/media.
Before running commands, review config diffs for the target version:
- Compare
.env.productionwith.env.production.example(includingAPP_URLandASSET_URL) - Check PHP settings from the target profile (
docker/prod/php.inior your custom PHP config) - Check Supervisor settings (
docker/general/supervisord.confor your deployment equivalent)
- Update source code to the target version:
cd opendatabio
git fetch --tags
git checkout <target-tag-or-branch>
- Build the new immutable images:
make build-prod
- Put the existing application in maintenance mode and run migrations with the new PHP image:
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan down
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml up -d mysql redis laravel
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan migrate --force
- Refresh Laravel caches and replace the web/worker containers:
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan optimize
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml up -d --force-recreate nginx supervisord
- Return the application to service and validate:
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan up
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml exec -T -u www-data laravel php artisan locales:audit
docker compose --env-file .env.production -p odb-prod -f docker-compose.prod.yml ps
Composer dependencies and frontend assets are built into the production images; do not run composer update or npm run build interactively inside production containers. If the new version introduces .env.production keys, add them before building/recreating containers.