# 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