# packaging Specification ## Purpose Defines how the dashboard is built into its container image and what the image guarantees to whoever runs it: location of the Dockerfile, build arguments, base images, user, ports, health check, architectures and size budget. ## Requirements ### Requirement: One Dockerfile per edition inside its package Edition: CE. The CE edition SHALL be built from `dashboard-ce/docker/Dockerfile`, with the workspace root (`openvidu-dashboard/`) as build context, so one `docker build` sees the lockfile and every package. #### Scenario: CE image built from the workspace root - **GIVEN** a checkout of the workspace - **WHEN** `docker build -f dashboard-ce/docker/Dockerfile .` runs from `openvidu-dashboard/` - **THEN** it produces a runnable CE image without any other input ### Requirement: Base href build argument Edition: CE. The Dockerfile SHALL accept the build argument `BASE_HREF` (default `/`) and build the SPA with it, so the application can be served under a path prefix such as `/dashboard/`. #### Scenario: Prefixed base href - **WHEN** the image is built with `--build-arg BASE_HREF=/dashboard/` - **THEN** the served `index.html` declares `` and its assets resolve under that prefix #### Scenario: Default base href - **WHEN** the image is built without `BASE_HREF` - **THEN** the served `index.html` declares `` ### Requirement: Reproducible dependency installation Edition: CE. The image build SHALL install dependencies with pnpm from the committed lockfile in frozen mode and SHALL use a Node 24 base image pinned to an exact version tag. #### Scenario: Lockfile out of date - **GIVEN** a `package.json` change not reflected in `pnpm-lock.yaml` - **WHEN** the image is built - **THEN** the build fails at the install step instead of resolving new versions ### Requirement: CE runtime is a plain Node image without private dependencies Edition: CE. The CE image SHALL run the built backend (which serves the SPA) on an official Node 24 image with only production dependencies and no private package; npm, npx, corepack and yarn SHALL be absent from the runtime image. #### Scenario: CE build needs no private access - **GIVEN** no build secret and no credentials for private repositories - **WHEN** the CE image is built - **THEN** the build succeeds #### Scenario: CE image carries no private package - **WHEN** the CE image filesystem is searched for packages of private repositories - **THEN** nothing is found ### Requirement: Non-root runtime user Edition: CE. The main process of the image SHALL run as a non-root user with uid 1000 and gid 1000. #### Scenario: Process identity - **WHEN** a container of the image is running - **THEN** its main process reports uid 1000 and the application files are readable by that user ### Requirement: Exposed port and container health check Edition: CE. The image SHALL declare `EXPOSE 5000` and a `HEALTHCHECK` that requests `GET /api/healthcheck` on `127.0.0.1:${SERVER_PORT}` (default 5000) without external tools. #### Scenario: Healthy container - **GIVEN** a container that has started listening - **WHEN** Docker evaluates the health check - **THEN** the container becomes `healthy` within the start period #### Scenario: Custom port - **GIVEN** a container started with `SERVER_PORT=6000` - **WHEN** Docker evaluates the health check - **THEN** the check targets port 6000 and the container becomes `healthy` ### Requirement: Multi-architecture images Edition: CE. The Dockerfile SHALL build for `linux/amd64` and `linux/arm64`: the platform-independent SPA build stage runs once on `$BUILDPLATFORM` and the other stages run per target platform (buildx with QEMU, no native arm64 runner). #### Scenario: Multi-platform build - **WHEN** the image is built with `--platform linux/amd64,linux/arm64` - **THEN** the resulting manifest list contains both platforms and each variant starts on its architecture ### Requirement: No timezone database in the images Edition: CE. The runtime image SHALL NOT contain `tzdata` (it is purged from the Node base image); timestamps are stored and logged in UTC and time zone conversion for bucketing uses the data bundled with Node. #### Scenario: Time zone conversion without tzdata - **GIVEN** a container with `TZ` unset and no `/usr/share/zoneinfo` - **WHEN** a time-series request specifies `tz=Europe/Madrid` - **THEN** buckets are aligned to that zone and log timestamps are UTC ### Requirement: Signal delivery to the application process Edition: CE. The image `CMD` SHALL use exec form so the application process is PID 1 and receives `SIGTERM` directly. #### Scenario: docker stop - **GIVEN** a running container of the image - **WHEN** `docker stop` is issued with the default 10 s timeout - **THEN** the container exits with code 0 before the timeout, without being killed ### Requirement: Image names and build wrapper Edition: CE. The image SHALL be named `openvidu/openvidu-dashboard`. `./dashboard.sh build-image ce` SHALL build it honouring `IMAGE_TAG` (default `dev`) and `BASE_HREF` (default `/`). #### Scenario: Wrapper builds CE - **WHEN** `./dashboard.sh build-image ce` runs - **THEN** the image `openvidu/openvidu-dashboard:dev` exists locally ### Requirement: Image size budget Edition: CE. The uncompressed `linux/amd64` CE image SHALL NOT exceed 300 MB; the image smoke test (`./dashboard.sh smoke-image ce`) enforces the budget. #### Scenario: Budget exceeded - **WHEN** a built image is larger than its budget - **THEN** the smoke test fails and reports the measured size ### Requirement: Supply-chain verification inside the image build Edition: CE. The Dockerfile SHALL install Aikido safe-chain in the build stage and run `npm safe-chain-verify` (with `SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS=0`, so only packages flagged as malicious fail the build) before installing dependencies, so the verification runs wherever the image is built. #### Scenario: Build with a flagged package - **GIVEN** a lockfile that resolves a package flagged as malicious by safe-chain - **WHEN** the image is built - **THEN** the build fails in the verification step before any dependency is installed