Operations

Deployment

Deploy externa-core like a standard Laravel + Vite app, then ensure Externa-specific workers, scheduler jobs, and AI/file storage are wired. Local setup details remain in Installation.

Deploy checklist

1. Environment secrets

Copy .env.example → production env store and set at least:

VariableProduction note
APP_ENVproduction
APP_DEBUGfalse
APP_KEYUnique; never reuse from another env
APP_URLCanonical URL users browse (prefer HTTPS; required for passkeys). No trailing-slash / proxy mismatch
DB_*Production database (see SQLite swap below)
SESSION_DRIVER / CACHE_STORE / QUEUE_CONNECTIONPrefer database or Redis — not array/sync
INITIAL_SUPER_ADMIN_*Strong password before first seed
AI_WEBHOOK_TOKENLong random secret if webhooks are used
LOCAL_AI_* / cloud provider keysPoint at a reachable gateway; do not commit keys
AI_REMOTE_IMPORT_HOSTSTight allowlist if remote import is enabled
MailReal MAIL_* if verification / password reset emails matter

Full catalog: Environment variables.

After changing env on a config-cached host:

php artisan config:cache
# or config:clear during debugging

2. Build & migrate

composer install --no-dev --optimize-autoloader
npm ci
npm run build
php artisan migrate --force

Use your platform’s release process (zero-downtime, maintenance mode, etc.) as appropriate.

3. Seed / sync permissions

Fresh database:

php artisan db:seed --force

Runs PermissionSeederRoleSeederCreateSuperAdminSeeder.

Existing database after permission enum changes:

php artisan permissions:sync
# optional cleanup:
php artisan permissions:sync --prune

Do not re-seed blindly in production if it would reset roles or recreate a known super-admin password unexpectedly. Prefer sync + controlled role admin UI.

php artisan storage:link

Required for public / assets disk URLs (/storage/...). See Files configuration. Ensure storage/ and bootstrap/cache/ are writable by the app user.

5. Queue worker (+ optional Reverb / Pulse)

Prefer Redis + Horizon when available:

php artisan horizon

Or a plain worker:

php artisan queue:work --tries=3 --timeout=300

If using the full realtime/observability stack, also supervise:

  • A Reverb (or compatible) WebSocket server with matching BROADCAST_* / REVERB_* / built VITE_REVERB_*
  • php artisan pulse:work when PULSE_INGEST_DRIVER=redis

Dashboard Health surfaces native Pulse/Horizon metrics (no primary Open Pulse/Horizon CTAs). Restart workers on every deploy. Details: Operations · Minimal vs full stack.

6. Scheduler crontab

* * * * * cd /path/to/externa-core && php artisan schedule:run >> /dev/null 2>&1

This runs:

  • activitylog:clean (daily)
  • files:cleanup-uploads / files:cleanup-zips (hourly)
  • ai:cleanup-attachments (daily)
  • ai:run-sync-sources (every minute)

7. AI gateway

If the assistant is enabled in production:

  1. Set AI_DEFAULT_PROVIDER (default local) and the matching provider keys/URL.
  2. Ensure the app server can reach LOCAL_AI_URL (or the cloud provider) with enough timeout for tool loops (AppAssistant uses 300s). Behind nginx/Herd, also raise fastcgi_read_timeout (often ≥ 600s) so long local-model reasoning does not drop the browser SSE.
  3. Prefer tool-calling-capable models.
  4. Set AI_WEBHOOK_TOKEN if using collection import webhooks; keep CSRF exception only for that path.

See AI configuration.

8. APP_URL and proxies

  • Match the public scheme/host (Herd, reverse proxy, CDN).
  • Set trusted proxies if TLS terminates upstream.
  • Optionally set FILE_PUBLIC_URL_BASE when asset URLs should use a CDN origin while the app stays on another host.
  • The CMS (password, TOTP, APIs) can run on HTTP, but prefer HTTPS in production.
  • Passkeys: require HTTPS in production; APP_URL must match Fortify WebAuthn relying_party_id / allowed_origins. See Passkeys — Requirements.

9. Production database (leave SQLite)

.env.example defaults to DB_CONNECTION=sqlite for local ease. For production:

  1. Provision MySQL 8+ or PostgreSQL.
  2. Set DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD.
  3. Run migrations against the new database.
  4. Keep session/cache/queue tables on a durable store (database or Redis).

SQLite ceilings

SQLite is fine for demos and single-user local work. Concurrent writers, large activity logs, and multi-worker queues push you toward MySQL/Postgres quickly.

Smoke test after deploy

  1. GET /up → healthy.
  2. Login as super admin (or your provisioned admin) — password and, if enabled, Sign in with passkey.
  3. Open dashboard, files, collections (permissions permitting).
  4. Trigger a small queued job (e.g. zip) and confirm the worker processes it.
  5. If AI is enabled: GET /ai/status and a short chat with a read-only tool.
  6. Confirm scheduled cleanups appear in logs after the next hour/day.
Previous
Minimal vs full stack