Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ on:
push:
branches: [main, preview, dev]
paths:
- "Dockerfile"
- "compose.yaml"
- ".dockerignore"
- "docker/**"
- "src/**"
- "bin/**"
- "tests/**"
Expand Down Expand Up @@ -180,6 +184,10 @@ jobs:
# start the workflow so the aggregate check exists, while these
# paths decide whether the expensive test jobs need to run.
ci:
- 'Dockerfile'
- 'compose.yaml'
- '.dockerignore'
- 'docker/**'
- 'src/**'
- 'bin/**'
- 'tests/**'
Expand Down Expand Up @@ -423,6 +431,7 @@ jobs:
run: |
bun x tsc --noEmit
bun x tsc --noEmit -p tests/tsconfig.doctor-service-memory-contract.json
bun x tsc --ignoreConfig --noEmit --strict --target ESNext --module ESNext --moduleResolution bundler --types bun-types --skipLibCheck scripts/ci/docker-smoke.ts

- name: GUI tests
run: cd gui && bun test --isolate tests
Expand Down Expand Up @@ -908,6 +917,26 @@ jobs:
bun run scripts/keyring-smoke.ts
'

# Exercise the source-build Compose contract, including real volume reuse.
# Host fixtures cannot prove image construction or container recreation.
docker-smoke:
name: docker smoke
needs: changes
if: github.event_name != 'pull_request' || needs.changes.outputs.ci == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false

- name: Setup project Bun
uses: ./.github/actions/setup-project-bun

- name: Build, start, and recreate the container
run: bun scripts/ci/docker-smoke.ts

npm-global-smoke:
name: npm-global ${{ matrix.os }}
needs: changes
Expand Down Expand Up @@ -986,7 +1015,7 @@ jobs:
# direct dependencies only, so a failing `select-windows-runner` would
# otherwise reach this gate as nothing at all while its dependents report
# `skipped` — which the gate is required to read as a deliberate skip.
needs: [changes, select-windows-runner, test, storage-policy, api-usage, gates, platform-macos, macos-control, platform-windows, keyring-smoke, npm-global-smoke]
needs: [changes, select-windows-runner, test, storage-policy, api-usage, gates, platform-macos, macos-control, platform-windows, keyring-smoke, docker-smoke, npm-global-smoke]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ RUN cd gui && bun run build
FROM ${BUN_IMAGE} AS runtime
WORKDIR /home/bun/app

# Docker supervises this foreground process; retain routed state on stop/recreate.
# This uses the existing service lifecycle mode and does not install a service manager.
ENV NODE_ENV=production \
OCX_SERVICE=1 \
OPENCODEX_HOME=/home/bun/.opencodex \
CODEX_HOME=/home/bun/.codex \
OCX_API_TOKEN_FILE=/home/bun/.opencodex/service-api-token
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Container lifecycle mode

Amendment after real Docker recreation verification. Docker supervises the foreground hub and must retain persisted routed state across replacement.

MODIFY Dockerfile runtime ENV: set existing OCX_SERVICE=1, with no service manager installation or privilege change. Preserve image digest, foreground CMD, listener authentication, separate writable homes and read-only root.
MODIFY scripts/ci/docker-smoke.ts: assert the actual container process receives service lifecycle mode. Retain the routed synthetic slug and exact token/catalog/config hashes across graceful recreation.
MODIFY tests/service/container-bootstrap.test.ts: include the runtime ENV declaration in the existing packaging contract.
MODIFY docs-site/src/content/docs/guides/remote-hub.md: document service-mode foreground lifecycle, Compose restart/recreation, and the limit on other dashboard restart paths.

Independent Astra high lifecycle/security review accepted the bounded packaging change. Actual remote CLI comparison confirmed preservation with service mode. Final image CI must prove the same real container lifecycle; no local tests or Docker execution. This does not change shared CLI cleanup, restart policy, or authentication code.
12 changes: 11 additions & 1 deletion docs-site/src/content/docs/guides/remote-hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@ opencodex does not publish an official container image. The repository does main
[`compose.yaml`](https://github.com/lidge-jun/opencodex/blob/main/compose.yaml), and a narrow
`.dockerignore`. The build pins the multi-platform Bun 1.4.0 image index by digest, runs the proxy as
the non-root `bun` user, keeps the root filesystem read-only, drops Linux capabilities, and publishes
only the data listener on the host's `127.0.0.1:10100` by default.
only the data listener on the host's `127.0.0.1:10100` by default. The foreground process uses
`OCX_SERVICE=1`, so stopping or recreating the container preserves routed Codex state instead
of restoring a native desktop configuration. Docker supplies supervision; no OS service manager
is installed in the image. Use Compose to restart/recreate the container; this does not extend
support to every dashboard restart path.

The image seeds a first-run `hub` configuration that binds the container listener to `0.0.0.0`.
Before the first normal start, stream a freshly generated data-plane token into the bootstrap helper.
Expand Down Expand Up @@ -287,6 +291,12 @@ unreadable, a non-loopback hub must not be accepted as ready. Never treat livene
`docker compose down --volumes` as destructive: it deletes configuration, OAuth credentials, usage
history, the data-plane token, and persisted Codex state together.

Cross-platform CI builds the source image and checks startup, data-plane token admission, and
container recreation using an isolated Compose project with throwaway credentials. It verifies that
both named volumes and a synthetic catalog survive replacement. This check does not validate a
real provider account, OAuth callback, custom mount migration, or every CPU architecture; perform
the authenticated routed-response check above for your deployment.

## Rollback

Inspect existing Serve mappings before changing them. `tailscale serve reset` removes every mapping
Expand Down
Loading
Loading