Container releases

August 28, 2026 ยท View on GitHub

container-release publishes ghcr.io/openai/codex-security from the default scanner Docker target. The scanner and findings service use this same image in separate containers with separate state. compose.findings.yaml supplies the Node server command, working directory, and service environment; the image's default command remains the scanner CLI.

Releases use the SDK package version, native Linux amd64/arm64 builds, BuildKit SBOMs and maximum-mode provenance, and a GitHub provenance attestation. Each native image passes scanner and findings API/persistence checks before publishing the multiarchitecture manifest. Anonymous pulls and attestation must succeed before promoting version, sha-<commit>, and latest tags. Stable version tags cannot be overwritten.

GHCR administrator setup

Before the first release, an administrator must prepare the package:

  1. Allow organization package creation and, if the package is missing, bootstrap it with a reviewed image and a non-release tag:

    docker build --target scanner -t ghcr.io/openai/codex-security:bootstrap .
    printf '%s' "$CR_PAT" | docker login ghcr.io --username YOUR_GITHUB_USER --password-stdin
    docker push ghcr.io/openai/codex-security:bootstrap
    docker logout ghcr.io
    

    Use a personal access token (classic) with write:packages, authorized for SSO if required; never commit it or pass it into the build.

  2. In the package's settings, link openai/codex-security, set visibility to Public, and grant the repository Write under Manage Actions access. The workflow uses GITHUB_TOKEN and refuses missing, private, or unreadable packages. Verify docker pull works after logging out of GHCR.

  3. Protect the repository's container environment with required reviewers and deployment rules for protected main and approved container-v* tags. Allow the workflow's pinned actions, package writes, and OIDC attestations. Update branch-protection check names if they reference the old release jobs.

See GitHub's registry authentication and package access settings.

Publishing

After merging to main, push container-v<version> matching the SDK package version or run container-release manually on main. Releases require a commit on protected main; pull requests only build and test.

If a release fails, fix the cause and rerun only failed jobs; do not overwrite an existing stable version. bootstrap and release-candidate-<commit> tags are not consumer releases.

See the findings service guide for image selection, source builds, storage, backups, and upgrades.

Migrating the findings service

The findings-service build target and separate ghcr.io/openai/codex-security-findings release are replaced by the shared image. For source builds, use --target scanner (or omit --target). Update compose.findings.yaml and set CODEX_SECURITY_FINDINGS_IMAGE to a published ghcr.io/openai/codex-security version or digest before pulling and starting the service. Custom deployments must also carry over the Compose file's Node entrypoint, server command, package working directory, and service environment.

Back up the service as described in the upgrade guide. Keep the same Compose project name and findings-state volume mounted at /state; do not run down --volumes. This packaging change does not migrate or move the database, and the runner must keep its own state. Existing published images and tags are unchanged.

Workflow runner

compose.runner.yaml runs the packaged CLI from the scanner image. It does not start a findings service or implement another workflow engine. It passes commands, output, and exit codes through the existing scanner entrypoint. The findings service uses the same image with the configuration described above.

Run these commands from the repository root. After the selected scanner release is available, prepare private directories and choose the host user's UID/GID so the runner can write its bind mounts:

mkdir -p results state
chmod 700 results state
export CODEX_SECURITY_USER="$(id -u):$(id -g)"
export CODEX_SECURITY_IMAGE=ghcr.io/openai/codex-security:latest
docker compose -f compose.runner.yaml pull
docker compose -f compose.runner.yaml run --rm codex-security login --device-auth

For unattended use, provide OPENAI_API_KEY or CODEX_API_KEY instead of login. Git authentication uses the existing GH_TOKEN/GITHUB_TOKEN and optional CODEX_SECURITY_GIT_HOST settings. Pass only the credentials the runner needs; the findings service's embedding credentials are configured separately. Use a version or digest in CODEX_SECURITY_IMAGE for repeatable deployments. To test an unreleased checkout, build the same scanner target locally instead of pulling:

docker build --target scanner -t codex-security:local .
export CODEX_SECURITY_IMAGE=codex-security:local

The existing CODEX_SECURITY_RESULTS and CODEX_SECURITY_STATE settings select the host directories (default ./results and ./state):

Container pathDurable contents
/outputScan artifacts and any source checkouts stored here
/output/.codex-security-stateCLI scan history and workbench database
/stateCodex sign-in and configuration

Keep all three across runner replacements. Keep the approved source checkout available at the same container path for later source reviews. For example, place a checkout under results/repository, then scan it with artifacts outside the checkout:

docker compose -f compose.runner.yaml run --rm codex-security \
  scan /output/repository --output-dir /output/scans/run-001 --headless

An existing checkout elsewhere can instead be bind-mounted with run --volume /absolute/repository:/input/repository; repeat that mount on each stage that needs the source. Moving a host scan's files into these directories does not rewrite absolute paths in its saved state. Run the scan in the runner or preserve its original paths. Never share the runner's workbench database or Codex home with the findings service's /state volume.

Connecting to the findings service

For an independently hosted service, pass its reachable base URL through the existing --findings-url flag. Container loopback addresses refer to the runner, not the Docker host or another container. The findings API has no authentication; use a private network or an authenticated TLS proxy appropriate to the deployment. Do not expose the unauthenticated API publicly.

For a service on the same Docker engine, start it as a separate Compose project:

docker compose -p findings -f compose.findings.yaml up -d

Save this network-only override as compose.runner.local.yaml:

networks:
  default:
    external: true
    name: findings_default

Then run the runner as a different project on that existing network. The service is reachable by its Compose DNS name even though its published host port remains loopback-only:

docker compose -p runner -f compose.runner.yaml -f compose.runner.local.yaml \
  run --rm codex-security dedupe --scan SCAN_ID \
  --findings-url http://findings:3000 --json

Use the scan ID from the completed scan and first import its findings into the service with the matching repository ID, as described in the findings API guide. For a remote service, omit the network override and supply its URL instead. Stopping or replacing the runner does not stop the service or remove its volume.

Only commands supported by the selected image are available. Workflow resumption, custom publication, and dedupe write-back require a release containing those SDK/CLI capabilities; durable mounts alone do not add them. The runner does not schedule, retry, or skip stages on its own.

Sandbox and lifecycle

The runner retains the scanner's nonroot user, dropped capabilities, no-new-privileges, and seccomp profile. It does not override Codex approval or filesystem settings. On hosts that restrict nested user namespaces, install the existing AppArmor profile and append -f compose.apparmor.yaml to the runner Compose commands. This override works because both examples use the codex-security service name. The entrypoint's bulk-scan-specific Landlock selection remains unchanged; it is not applied to other commands. Source inspection needs a host that supports the selected Codex sandbox; do not disable sandboxing to work around host restrictions.

run --rm removes only the finished runner container. Preserve its host mounts for later stages and retries; use the same image version and source paths. Stop active runners before backing up the entire results and state directories, and back up the findings service separately. No service ports or Docker socket are exposed by the runner example.