Operations

Deploy with Docker

Operator path for self-hosted Externa: pull a published image (or build once), set a small .env, docker compose up, healthy stack. Mirrors the Directus-style friction goal.

Related

Quick deploy (pull GHCR)

Published multi-arch image: ghcr.io/qiick-io/externa-core (linux/amd64 + linux/arm64). CI pushes on version tags (v*) and optional workflow dispatch.

# From an externa-core checkout (Compose + env sample live in the repo)
cp .env.docker.quick.example .env

# Required: APP_KEY, strong DB_PASSWORD, APP_URL
php -r "copy('.env', '.env.bak'); file_put_contents('.env', preg_replace('/^APP_KEY=.*/m', 'APP_KEY=base64:'.base64_encode(random_bytes(32)), file_get_contents('.env')));"

# First boot only — create roles + super admin (then set RUN_SEED=false)
# RUN_SEED=true
# INITIAL_SUPER_ADMIN_EMAIL=you@example.com
# INITIAL_SUPER_ADMIN_PASSWORD='…strong…'

docker compose -f compose.quick.yaml up -d

Open http://localhost:8080 (host APP_PORT, default 8080→80). Reverb publishes on host 8081.

PieceDefault
Imageghcr.io/qiick-io/externa-core:latest (EXTERNA_IMAGE override)
Servicesapp, horizon, reverb, scheduler, pulse, pgsql, redis
Volumesexterna_quick_storage, externa_quick_pgsql, externa_quick_redis
HealthcheckGET /health/ready on app (start_period: 90s)

Pin a release:

EXTERNA_IMAGE=ghcr.io/qiick-io/externa-core:1.0.0-beta.4 \
  docker compose -f compose.quick.yaml up -d

Local smoke without GHCR pull (bake once, then quick Compose):

docker build --target production -t externa:prod .
EXTERNA_IMAGE=externa:prod docker compose -f compose.quick.yaml up -d

Minimal .env

VariableRequiredNotes
APP_KEYyesbase64:… from key:generate / random 32 bytes
APP_URLyesPublic URL browsers hit (match proxy / port)
DB_PASSWORDyesShared with Compose pgsql
DB_*defaultspgsql / externa / host pgsql inside Compose
INITIAL_SUPER_ADMIN_*when seedingEmail + strong password before RUN_SEED=true
RUN_MIGRATIONSsample trueEntrypoint runs migrate --force only when true
RUN_SEEDsample falseOpt-in db:seed --force (roles + super admin)
REVERB_*sampleChange app key/secret for anything beyond laptop demos
TRUSTED_PROXIESbehind TLS proxy* or CIDRs — see Reverse proxy

Full catalog: Environment variables — Docker Compose knobs.

First boot (gated)

Entrypoint defaults RUN_MIGRATIONS=false and RUN_SEED=false — restarts do not surprise-mutate the database unless Compose/env opts in.

  1. First bring-up: keep RUN_MIGRATIONS=true (quick/prod samples).
  2. Optional admin bootstrap: set strong INITIAL_SUPER_ADMIN_*, then RUN_SEED=true for one start.
  3. After login works: set RUN_SEED=false (and optionally RUN_MIGRATIONS=false once schema is stable).
  4. Seeders are idempotent on permissions/roles/super-admin email — still treat seed-on-boot as a bootstrap flag, not a forever default.

No automatic destructive wipe. migrate --force only applies pending migrations.

Healthchecks

Compose app probes GET /health/ready (DB + required Redis). Also available:

EndpointRole
GET /upLaravel liveness
GET /health/liveProcess up
GET /health/readyReady for traffic (503 when checks fail)

Details: Deployment — Health probes.

Publish / multi-arch path

# Bake amd64+arm64 (no push)
docker buildx bake -f docker-bake.hcl production
# or
./scripts/docker-buildx.sh production

# Push to GHCR (needs docker login ghcr.io)
VERSION=1.0.0-beta.4 PUSH=1 ./scripts/docker-buildx.sh production

CI: workflow dockercompose-config (includes compose.quick.yaml), single-arch image-build, multiarch-bake on PR/push, ghcr-publish on v* tags (+ workflow_dispatch).

Build-from-source production

When you need to bake assets yourself (custom Vite Reverb public URL, patches):

cp .env.docker.prod.example .env
# APP_KEY, secrets, APP_URL, TRUSTED_PROXIES, browser-facing REVERB_*
docker compose -f compose.prod.yaml up --build -d

Same healthchecks and gated RUN_MIGRATIONS / RUN_SEED as quick deploy.

Follow-through checklist

PathWhenLink
Managed DB / RedisPostgres/Redis outside Compose (RDS, Upstash, …)compose.prod.yaml + compose.prod.managed.yamlDeployment — Managed · core #15
Reverse proxy / TLSPublic HTTPS + WebSocket Upgrade to ReverbReverse proxy cookbook · core #17
Local full stackDev with Vite/Mailpit/MinIOInstallation — Docker
Bare metalNo ComposeDeployment

Smoke after up

  1. curl -fsS http://localhost:8080/health/ready"status":"ok".
  2. Login with INITIAL_SUPER_ADMIN_* (if seeded) or your provisioned admin.
  3. Confirm Horizon/scheduler containers are up; trigger a small queued job if useful.
  • Core README Docker sections · samples: .env.docker.quick.example, .env.docker.prod.example, .env.docker.prod.managed.example
  • Upgrade · Backup & restore · Operations
Previous
Deployment