Docker Installation

How to install OpenDataBio with Docker

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:

  1. docker-compose.yml: development and local testing, with source bind mounts, phpMyAdmin, and ports 8081/8082.
  2. 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-init starts the development profile and uses .env;
  • make init-prod starts 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.

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:

  1. generates APP_KEY only when it is empty;
  2. builds the frontend once and copies the same generated assets into the self-contained PHP and nginx images;
  3. waits for MySQL and Redis healthchecks;
  4. runs migrations;
  5. configures interface and user-content locales;
  6. caches Laravel configuration/routes/views;
  7. starts nginx and queue workers;
  8. 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:

  1. Docker with the Compose v2 plugin (docker compose).
  2. Linux/macOS: a user allowed to access the Docker socket, or use a rootless Docker installation.
  3. Windows: Docker Desktop with WSL2/Hyper-V.
  4. make for the short commands below.
  5. Node.js 22 and npm on the host. The development source bind mount replaces the image’s application tree, so make docker-init builds 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

  1. make docker-init - copy .env.docker if .env is absent, build/start containers, install dependencies, generate a missing key, migrate, and create the storage link
  2. make build - build containers
  3. make key-generate - generate an app key only if one does not already exist
  4. make composer-install - install PHP dependencies
  5. make composer-update - update php dependencies
  6. make composer-dump-autoload - execute composer dump-autoload within container
  7. make migrate - create or update the database
  8. make drop-migrate - delete and recreate the database
  9. make seed-odb - seed the database with locations and taxons
  10. make seed-prod - seed a production Docker database without touching host storage
  11. make init-prod - build and initialize the production profile
  12. make start-prod / make stop-prod - start or stop the production profile

Container access

  1. make start - start all containers
  2. make stop - stop all containers
  3. make restart - restart all containers
  4. make ssh - enter the main laravel app container
  5. make ssh-mysql - enter the mysql container, so you may the log to the database using mysql -uUSER -pPWD
  6. make mysql - enter the docker mysql console
  7. make ssh-nginx - enter the nginx container
  8. make ssh-supervisord - enter the supervisord container

Maintenance commands

  1. make optimize - clean caches and log files
  2. make info - show app info
  3. make logs - show laravel logs
  4. make logs-mysql - show mysql logs
  5. make logs-nginx - show nginx logs
  6. make 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

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.production with .env.production.example (including APP_URL and ASSET_URL)
  • Check PHP settings from the target profile (docker/prod/php.ini or your custom PHP config)
  • Check Supervisor settings (docker/general/supervisord.conf or your deployment equivalent)
  1. Update source code to the target version:
cd opendatabio
git fetch --tags
git checkout <target-tag-or-branch>
  1. Build the new immutable images:
make build-prod
  1. 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
  1. 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
  1. 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.