Skip to main content

Deployment and Operations

The long-term deployment topology should continue to converge around:

Nginx
-> Laravel API
-> React SPA static assets
MariaDB
Redis
Analysis Worker
Docker Compose

Frontend transition note

The repository previously contained two frontend lines during the Nuxt-to-SPA transition. As of v1.4.3, the React/Vite SPA is the canonical frontend/ directory and the older Nuxt/Vue implementation has been removed from the active runtime tree.

The historical v1.1 transition work is retained in the roadmap documentation, but deployment operations should now use the normalized v1.4.3 layout described below.

Laravel runtime requirements

  • Laravel remains the unified backend entry point
  • database initialization uses migrations and seeders
  • the old lightweight PHP implementation remains only as historical reference in backend/legacy-lightweight/

Database initialization

After starting services, the recommended command is:

docker compose exec php php /var/www/html/artisan migrate --seed --force

To reset the database:

docker compose exec php php /var/www/html/artisan migrate:fresh --seed --force

Analysis Worker and Redis boundary

Current operating assumptions:

  • analysis_jobs are persisted in MariaDB
  • Redis list ANALYSIS_JOB_REDIS_QUEUE is the async worker handoff boundary
  • analysis-worker processes analysis workloads
  • the default YOLO model path is analysis-worker/models/uprc2018/best.pt

Operationally, this should be understood as:

  1. Laravel creates and queries jobs
  2. Laravel pushes queued job IDs to Redis after durable database creation
  3. Analysis Worker consumes Redis, executes supported jobs, and reports results back through Laravel APIs

The default queue name is:

REDIS_PREFIX=
ANALYSIS_JOB_REDIS_QUEUE=ocean:analysis-jobs:queued

REDIS_PREFIX should remain empty for this worker handoff path so Laravel and the analysis worker read and write the same Redis list name.

If Redis is temporarily unavailable during job creation, the durable database row is still preserved. Operators can retry failed jobs or requeue jobs once Redis is healthy.

Common validation commands

Governance actor context

curl -s -X POST http://127.0.0.1:8080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"password"}'
curl -s -H 'X-Ocean-Actor-Id: 3' http://127.0.0.1:8080/api/governance/me
curl -s http://127.0.0.1:8080/api/governance/roles

For v1.4.0, the SPA authenticates with POST /api/auth/login and sends Authorization: Bearer <token> for protected mutation routes. X-Ocean-Actor-Id is an internal identity-injection bridge, not a public authentication mechanism. It remains available for non-SPA tooling during the transition, but protected user mutation routes require bearer tokens.

Analysis Worker callbacks use an internal bridge header while the project waits for a real worker credential:

curl -s -H 'X-Ocean-Worker: ocean-analysis-worker' http://127.0.0.1:8080/api/analysis-jobs

Audit events

curl -s http://127.0.0.1:8080/api/audit-events?page_size=20
curl -s 'http://127.0.0.1:8080/api/audit-events?resource_type=analysis_job'

Use audit events to verify high-value actions such as task start/submit, sample result creation, exception resolution, and analysis job lifecycle transitions.

Settings and user management

After logging in as admin, capture the bearer token and validate the v1.4.1 governance pages through the API:

TOKEN="$(
curl -s -X POST http://127.0.0.1:8080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"password"}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["token"])'
)"

curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/profile
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/settings
curl -s -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/api/users?page_size=20'

curl -s -X PATCH http://127.0.0.1:8080/api/settings \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"language":"zh-Hans","display_density":"comfortable","default_workspace_tab":"settings"}'

To verify audit coverage for user/profile/settings changes:

curl -s 'http://127.0.0.1:8080/api/audit-events?resource_type=user&page_size=20'

Dashboard summary

curl -s http://127.0.0.1:8080/api/dashboard/summary

Inspection tasks

curl -s http://127.0.0.1:8080/api/inspection-tasks

Laravel API route list

docker exec ocean-php php /var/www/html/artisan route:list --path=api

Migration status

docker exec ocean-php php /var/www/html/artisan migrate:status

Analysis queue depth

docker exec ocean-redis redis-cli LLEN ocean:analysis-jobs:queued

Documentation site deployment

The documentation site lives in website/, uses Docusaurus, and is deployed independently to GitHub Pages.

The delivery rules are:

  • documentation build stays separate from business service builds
  • documentation can be published independently
  • English is the default locale and Simplified Chinese remains available through i18n

Default SPA static hosting

The default Compose path now serves the React/Vite workspace frontend:

  1. frontend builds from docker/frontend/Dockerfile
  2. the SPA image serves static assets on port 80 with history fallback to index.html
  3. top-level Nginx proxies / to the SPA container
  4. top-level Nginx routes /api/ to the Laravel / PHP entry point

The relevant files are:

  • docker/nginx/default.conf
  • docker/frontend/Dockerfile
  • docker/frontend/nginx.conf
  • docker/compose.local.yml
  • root docker-compose.yml compatibility include

The earlier Nuxt implementation has been removed from the active repository layout. Historical context remains in the docs and git history.

The default local Compose service for asynchronous analysis is named analysis-worker, and its source directory is also analysis-worker/.

v1.4.2 production image direction

The next deployment hardening step is to package the main web application as one production image while keeping infrastructure and analysis workloads isolated.

Recommended production topology:

app
- Nginx
- PHP-FPM
- Laravel API
- built React/Vite SPA

db
redis
analysis-worker

The intended app image should replace the current production need for separate frontend, nginx, and php containers. It should not include MariaDB, Redis, or the analysis worker process.

The target image should be built with a multi-stage flow:

  1. build frontend/ with pnpm install --frozen-lockfile and pnpm run build
  2. install backend Composer dependencies with --no-dev --optimize-autoloader
  3. copy Laravel source, vendor dependencies, and SPA build output into a runtime image
  4. run Nginx and PHP-FPM together through an entrypoint or supervisor

Production Nginx should serve SPA files directly with history fallback and route /api/ to Laravel public/index.php through local PHP-FPM. Runtime configuration must come from environment variables; do not bake .env secrets into the image.

The production path uses analysis-worker as both service and source directory name, because the service role is asynchronous analysis execution, image/model inference, and result write-back. Python remains the implementation language, not the deployment-facing product role.

Storage needs special handling: Laravel public/uploads storage should remain persistent and, when image analysis jobs require local files, should be mounted into analysis-worker with the same semantics currently used by OCEAN_STORAGE_ROOT.

Migrations should remain an explicit deployment step, for example:

docker compose -f docker/compose.prod.yml run --rm app php artisan migrate --force

Do not silently run migrations on every web container boot unless the release process explicitly adopts that policy. The app image supports an explicit opt-in switch, OCEAN_RUN_MIGRATIONS=true, for controlled environments that intentionally want startup migrations.

v1.4.3 repository layout direction

v1.4.3 normalized file locations without changing the product runtime responsibilities introduced in v1.4.2.

The active layout is:

backend/ Laravel API
frontend/ active React/Vite SPA
analysis-worker/ async analysis worker implemented in Python
docker/ Compose, Nginx, app image, and worker image assets
website/ Docusaurus documentation site

The old Nuxt/Vue implementation no longer occupies the active frontend/ path. Historical context is kept in documentation and git history rather than as a full runnable app. The obsolete SPA Compose example has been removed.

Root-level Compose files are minimized. Use explicit Docker paths such as docker/compose.local.yml and docker/compose.prod.yml; the root docker-compose.yml remains only as a lightweight compatibility include for local onboarding.

v1.4.4 release image publishing

Release tags now publish the deployable image pair for the production topology:

  • app: Nginx + PHP-FPM + Laravel API + built React/Vite SPA
  • analysis-worker: asynchronous analysis, image/model inference, and result write-back runtime

MariaDB and Redis remain separate stateful services and are not included in the release images.

GitHub releases publish to GitHub Container Registry:

ghcr.io/<owner>/<repo>/app:<release-tag>
ghcr.io/<owner>/<repo>/app:latest
ghcr.io/<owner>/<repo>/analysis-worker:<release-tag>
ghcr.io/<owner>/<repo>/analysis-worker:latest

CNB tag releases publish the same image pair to the CNB Docker registry under the repository slug:

$CNB_DOCKER_REGISTRY/$CNB_REPO_SLUG/app:<release-tag>
$CNB_DOCKER_REGISTRY/$CNB_REPO_SLUG/app:latest
$CNB_DOCKER_REGISTRY/$CNB_REPO_SLUG/analysis-worker:<release-tag>
$CNB_DOCKER_REGISTRY/$CNB_REPO_SLUG/analysis-worker:latest

Use immutable release tags for production rollouts. Treat latest as a convenience pointer for smoke testing or controlled environments that intentionally follow the newest published release.

The backend development Docker configuration path is docker/backend/. The local Compose service can still be named php for compatibility with existing commands, but new file-path references should use docker/backend/ rather than the old docker/php/ name.

The project should not continue to treat Nuxt SSR / Nitro as the long-term deployment mainline, because:

  • it offers limited value for an internal management workspace
  • a long-running Node runtime increases deployment and troubleshooting complexity
  • the separation of docs, workspace UI, and API is cleaner with SPA + Laravel