Docker runners (optional "works in Linux" checks)

These Docker runners split into two buckets:

The live-model Docker runners also bind-mount only the needed CLI auth homes (or all supported ones when the run is not narrowed), then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:

Full Release Validation includes both cold-storage cases in its stable and full Docker release-path profiles: session-runtime-context belongs to core, and openai-chat-tools belongs to package-update-openai. Both use the candidate package installed in the shared functional image. These bounded cases complement the storage unit tests by checking packaged workers, Gateway configuration and history requests, filesystem recovery, and a real provider turn; they do not repeat a large database stress campaign on every release.

When newer tooling validates an explicitly authorized older frozen target that predates cold storage, compatibility preflight records the cold subcases as unsupported and the lanes report NOT RUN for them. The original checks still run, and current targets always require cold-storage coverage.

To prebuild and reuse the shared functional image manually:

APUS_DOCKER_E2E_IMAGE=apus-docker-e2e-functional:local pnpm test:docker:e2e-build
APUS_DOCKER_E2E_IMAGE=apus-docker-e2e-functional:local APUS_SKIP_DOCKER_BUILD=1 pnpm test:docker:mcp-channels

Suite-specific image overrides such as APUS_GATEWAY_NETWORK_E2E_IMAGE still win when set. When APUS_SKIP_DOCKER_BUILD=1 points at a remote shared image, the scripts pull it if it is not already local. The QR and installer Docker tests keep their own Dockerfiles because they validate package/install behavior rather than the shared built-app runtime.

The live-model Docker runners also bind-mount the current checkout read-only and stage it into a temporary workdir inside the container. This keeps the runtime image slim while still running Vitest against your exact local source/config. The staging step skips large local-only caches and app build outputs such as .pnpm-store, .worktrees, __apus_vitest__, and app-local .build or Gradle output directories so Docker live runs do not spend minutes copying machine-specific artifacts. They also set APUS_SKIP_CHANNELS=1 so gateway live checks do not start real Telegram/Discord/etc. channel workers inside the container. test:docker:live-models still runs pnpm test:live, so pass through APUS_LIVE_GATEWAY_* as well when you need to narrow or exclude gateway live coverage from that Docker lane.

test:docker:openwebui is a higher-level compatibility smoke: it starts an Apus gateway container with the OpenAI-compatible HTTP endpoints enabled, starts a pinned Open WebUI container against that gateway, signs in through Open WebUI, verifies /api/models exposes apus/default, then sends a real chat request through Open WebUI's /api/chat/completions proxy. Set OPENWEBUI_SMOKE_MODE=models for release-path CI checks that should stop after Open WebUI sign-in and model discovery, without waiting on a live model completion. The first run can be noticeably slower because Docker may need to pull the Open WebUI image and Open WebUI may need to finish its own cold-start setup. This lane expects a usable live model key, provided through the process environment, staged auth profiles, or an explicit APUS_PROFILE_FILE. Successful runs print a small JSON payload like { "ok": true, "model": "apus/default", ... }.

test:docker:mcp-channels is intentionally deterministic and does not need a real Telegram, Discord, or iMessage account. It boots a seeded Gateway container, starts a second container that spawns apus mcp serve, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio MCP bridge. The notification check inspects the raw stdio MCP frames directly so the smoke validates what the bridge actually emits, not just what a specific client SDK happens to surface.

test:docker:agent-bundle-mcp-tools is deterministic and does not need a live model key. It builds the repo Docker image, starts a real stdio MCP check server inside the container, materializes that server through the embedded Apus bundle MCP runtime, executes the tool, then verifies coding and messaging keep bundle-mcp tools while minimal and tools.deny: ["bundle-mcp"] filter them.

test:docker:cron-mcp-cleanup is deterministic and does not need a live model key. It starts a seeded Gateway with a real stdio MCP check server, runs an isolated cron turn and a sessions_spawn one-shot child turn, then verifies the MCP child process exits after each run.

Manual ACP plain-language thread smoke (not CI):

Useful env vars: