Area
Installation or packaging
What are you trying to accomplish?
OpenCodex is widely used not only as a local CLI proxy, but also as a centralized remote hub (on Linux VPS, homelab servers, Docker Compose, and Kubernetes/k3s clusters), following the official Remote Hub Deployment Guide.
Currently, operators who run OpenCodex in containers have to maintain their own external packaging repositories (such as nordz0r/opencodex-docker) with custom scheduled cron workflows to detect new npm/GitHub releases, build the Vite GUI, compile multi-arch images, and push them to container registries.
We would like OpenCodex to provide an official multi-arch container image (linux/amd64 and linux/arm64) published directly to GitHub Container Registry (ghcr.io/lidge-jun/opencodex:<tag> and :latest) upon every release promotion.
What prevents this today?
- No official Dockerfile: The repository currently has no container definition. Users wishing to run OpenCodex under Docker or Kubernetes must write their own Dockerfiles, determining how to correctly build the Vite dashboard (
gui/), pin Bun versions, set environment variables (OPENCODEX_HOME), and configure file permissions.
- Duplicated community effort & release lag: Community packaging repositories must poll for npm/GitHub releases with periodic watchdog workflows. This introduces latency between when an upstream release is published and when the container image becomes available, as well as redundant compute across different operators.
- Trust and security: Self-hosters prefer pulling signed or official images from the upstream repository namespace (
ghcr.io/lidge-jun/opencodex) rather than relying on third-party community builds.
What should OpenCodex do?
-
Add a production-ready, security-hardened Dockerfile at the root of the repository:
- Multi-stage build:
- Stage 1 (builder): Installs dependencies with
bun install --frozen-lockfile, builds the Vite dashboard (cd gui && bun install --frozen-lockfile && bun run build), and prunes dev dependencies.
- Stage 2 (runtime): Minimal runtime using
oven/bun (pinned by digest), copies only the necessary production artifacts (package.json, bun.lock, node_modules, src/, gui/dist/, bin/), creates a dedicated non-root user (USER bun), and defines VOLUME ["/home/bun/.opencodex"].
- Container Healthcheck: Built-in
HEALTHCHECK testing http://127.0.0.1:10100/healthz.
- Default Entrypoint / CMD: Starts the hub on default port
10100 (bun run src/cli/index.ts start --port 10100).
-
Add a GitHub Actions workflow (.github/workflows/docker-publish.yml) that:
- Triggers automatically when a release is published (
types: [published]) or via workflow_dispatch.
- Sets up QEMU and Docker Buildx for multi-architecture builds (
linux/amd64,linux/arm64).
- Leverages GitHub Actions cache (
type=gha) for fast builds.
- Runs a quick container smoke test on the built image (
/healthz and /readyz return 200) prior to publishing.
- Publishes tagged images to GHCR (e.g.
ghcr.io/lidge-jun/opencodex:v2.50.0, ghcr.io/lidge-jun/opencodex:2.50.0, and ghcr.io/lidge-jun/opencodex:latest).
Example usage or interface
Here is a complete, production-tested implementation that has been used in production across multiple minor and major OpenCodex versions (from v2.40.0 to v2.50.0):
1. Proposed Dockerfile
# syntax=docker/dockerfile:1
ARG BUN_IMAGE=oven/bun:1.4.0@sha256:5ff609364c049b54eb0ff560ec96319729a972078ef2c755d758f0c6ef89c2d6
# Stage 1: Build dashboard and prepare production dependencies
FROM ${BUN_IMAGE} AS build
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /home/bun/app
COPY package.json bun.lock ./
RUN --mount=type=cache,target=/home/bun/.bun/install/cache \
bun install --frozen-lockfile
# Build the GUI dashboard (vite)
COPY gui/package.json gui/bun.lock* ./gui/
RUN cd gui && bun install --frozen-lockfile
COPY . .
RUN cd gui && bun run build
# Remove development-only toolchains before runtime stage
RUN rm -rf node_modules/@typescript node_modules/typescript
# Stage 2: Minimal runtime
FROM ${BUN_IMAGE} AS runtime
RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
WORKDIR /home/bun/app
ENV OPENCODEX_HOME=/home/bun/.opencodex \
NODE_ENV=production
COPY --from=build --chown=bun:bun /home/bun/app/package.json ./package.json
COPY --from=build --chown=bun:bun /home/bun/app/bun.lock ./bun.lock
COPY --from=build --chown=bun:bun /home/bun/app/node_modules ./node_modules
COPY --from=build --chown=bun:bun /home/bun/app/src ./src
COPY --from=build --chown=bun:bun /home/bun/app/gui/dist ./gui/dist
COPY --from=build --chown=bun:bun /home/bun/app/bin ./bin
RUN mkdir -p /home/bun/.opencodex && chown -R bun:bun /home/bun
USER bun
VOLUME ["/home/bun/.opencodex"]
EXPOSE 10100
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD bun -e "const r=await fetch('http://127.0.0.1:10100/healthz');if(!r.ok)process.exit(1)"
CMD ["bun", "run", "src/cli/index.ts", "start", "--port", "10100"]
2. Proposed GitHub Actions Workflow (.github/workflows/docker-publish.yml)
name: Publish Container Image
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g. v2.50.0)'
required: false
type: string
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
docker:
name: Build & Publish Multi-Arch Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
3. User Experience (Docker Compose)
End users and hub administrators can simply run:
services:
opencodex:
image: ghcr.io/lidge-jun/opencodex:latest
container_name: opencodex-hub
restart: unless-stopped
ports:
- "10100:10100"
volumes:
- ./data:/home/bun/.opencodex
Alternatives or workarounds
- Third-party community packaging: Users currently rely on community images like
nordz0r/opencodex from nordz0r/opencodex-docker. While functional, it requires maintenance outside the core repository and creates an unnecessary third-party trust dependency for end users.
- Manual local build / systemd service: Running directly on the host using Node/Bun. This is harder to manage in orchestrated container environments (Kubernetes, Nomad, TrueNAS, unRAID, etc.).
Additional context
- Upstream documentation already emphasizes headless remote hub setups (Remote Hub Deployment). An official container image is the natural companion to this guide.
- The built-in
/healthz and /readyz endpoints make Kubernetes Liveness and Readiness probes straightforward.
- Happy to submit this as a Pull Request to
dev if maintainers agree with the proposed layout.
Checks
Area
Installation or packaging
What are you trying to accomplish?
OpenCodex is widely used not only as a local CLI proxy, but also as a centralized remote hub (on Linux VPS, homelab servers, Docker Compose, and Kubernetes/k3s clusters), following the official Remote Hub Deployment Guide.
Currently, operators who run OpenCodex in containers have to maintain their own external packaging repositories (such as
nordz0r/opencodex-docker) with custom scheduled cron workflows to detect new npm/GitHub releases, build the Vite GUI, compile multi-arch images, and push them to container registries.We would like OpenCodex to provide an official multi-arch container image (
linux/amd64andlinux/arm64) published directly to GitHub Container Registry (ghcr.io/lidge-jun/opencodex:<tag>and:latest) upon every release promotion.What prevents this today?
gui/), pin Bun versions, set environment variables (OPENCODEX_HOME), and configure file permissions.ghcr.io/lidge-jun/opencodex) rather than relying on third-party community builds.What should OpenCodex do?
Add a production-ready, security-hardened
Dockerfileat the root of the repository:bun install --frozen-lockfile, builds the Vite dashboard (cd gui && bun install --frozen-lockfile && bun run build), and prunes dev dependencies.oven/bun(pinned by digest), copies only the necessary production artifacts (package.json,bun.lock,node_modules,src/,gui/dist/,bin/), creates a dedicated non-root user (USER bun), and definesVOLUME ["/home/bun/.opencodex"].HEALTHCHECKtestinghttp://127.0.0.1:10100/healthz.10100(bun run src/cli/index.ts start --port 10100).Add a GitHub Actions workflow (
.github/workflows/docker-publish.yml) that:types: [published]) or viaworkflow_dispatch.linux/amd64,linux/arm64).type=gha) for fast builds./healthzand/readyzreturn 200) prior to publishing.ghcr.io/lidge-jun/opencodex:v2.50.0,ghcr.io/lidge-jun/opencodex:2.50.0, andghcr.io/lidge-jun/opencodex:latest).Example usage or interface
Here is a complete, production-tested implementation that has been used in production across multiple minor and major OpenCodex versions (from v2.40.0 to v2.50.0):
1. Proposed
Dockerfile2. Proposed GitHub Actions Workflow (
.github/workflows/docker-publish.yml)3. User Experience (Docker Compose)
End users and hub administrators can simply run:
Alternatives or workarounds
nordz0r/opencodexfromnordz0r/opencodex-docker. While functional, it requires maintenance outside the core repository and creates an unnecessary third-party trust dependency for end users.Additional context
/healthzand/readyzendpoints make Kubernetes Liveness and Readiness probes straightforward.devif maintainers agree with the proposed layout.Checks