Repository navigation
[Feature]: Official multi-arch Docker image and automated build workflow (GHCR / Docker Hub) #4198
Description
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
Dockerfileat 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 definesVOLUME ["/home/bun/.opencodex"].
- Stage 1 (builder): Installs dependencies with
- Container Healthcheck: Built-in
HEALTHCHECKtestinghttp://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).
- Multi-stage build:
-
Add a GitHub Actions workflow (
.github/workflows/docker-publish.yml) that:- Triggers automatically when a release is published (
types: [published]) or viaworkflow_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 (
/healthzand/readyzreturn 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, andghcr.io/lidge-jun/opencodex:latest).
- Triggers automatically when a release is published (
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=max3. 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/.opencodexAlternatives or workarounds
- Third-party community packaging: Users currently rely on community images like
nordz0r/opencodexfromnordz0r/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
/healthzand/readyzendpoints make Kubernetes Liveness and Readiness probes straightforward. - Happy to submit this as a Pull Request to
devif maintainers agree with the proposed layout.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
lidge-jun commentedon Sep 10, 2026
리뷰 · 우선순위 48 / 80
이 이슈는 “공식 multi-arch 컨테이너 이미지를 릴리스마다 GHCR(및 선택적으로 Docker Hub)에 올려 달라”는 설치/패키징 요청이다. 제안 내용은 루트
Dockerfile,.github/workflows/docker-publish.yml, Compose 사용 예시까지 구체적이고, 커뮤니티 저장소nordz0r/opencodex-docker가 지금 그 공백을 메우고 있다는 맥락도 분명하다. remote hub 가이드를 쓰는 VPS·homelab·k3s 운영자 입장에서는 npm/docker compose build보다ghcr.io/lidge-jun/opencodex:<tag>를 pull 하는 편이 신뢰·지연·중복 빌드 면에서 낫다는 주장이다.다만 현재
devHEAD6d3ad12e3에는 이미 공식 소스빌드 컨테이너 스택이 있다. 루트Dockerfile은 multi-stage + digest-pinnedoven/bun:1.4.2, GUI 빌드, non-rootbun,/healthzHEALTHCHECK,OCX_SERVICE=1, 분리된OPENCODEX_HOME/CODEX_HOME볼륨, 호환성 매니페스트 검증(docker/verify-compatibility.ts,src/generated/compatibility-version.json)까지 포함한다.compose.yaml과.dockerignore도 있고,docs-site/src/content/docs/guides/remote-hub.mdDocker Compose 절이 그 경로를 문서화한다. CI에는docker-smoke잡이 있어 이미지 기동·토큰·볼륨 재생성까지 본다. 즉 이슈 본문의 “저장소에 Dockerfile이 없다”는 전제는 이미 틀렸다. 가이드 문장 그대로 “공식 게시(publish) 이미지는 없고, 소스빌드 Dockerfile/Compose는 유지한다”가 현재 정책이다.그래서 남는 진짜 요청은 Dockerfile 신설이 아니라 릴리스 프로모션에 묶인 공식 레지스트리 publish다. 지금
release.yml은 npm publish 중심이고, GHCR push 워크플로는 없다. 제안 Dockerfile을 그대로 넣으면 오히려 역행한다. 커뮤니티 초안은 Bun 1.4.0,bin/복사, 단순VOLUME, 호환성 매니페스트·시드 config·bootstrap-token·read_only Compose 보안 옵션이 HEAD 공식 이미지와 어긋난다. 받아도 되는 조각은 “release published / workflow_dispatch → buildx amd64+arm64 → smoke →ghcr.io/lidge-jun/opencodex태그” 자동화와, 가이드의 “does not publish” 문장을 정책 변경에 맞게 고치는 일이다.인접 이슈와의 관계도 정리해야 한다. 열린
#4179는 remote Git context에서 자기완결 Compose 빌드(호스트에서generate-compatibility-version.ts를 먼저 돌리지 않기)를 요구한다. GHCR에 빌드된 이미지를 올리면#4179의 일부 고통(서버에서 소스 준비)은 완화되지만, 매니페스트 생성·컨텍스트 제약은 빌드 파이프라인 안으로 옮겨질 뿐 사라지지 않는다. 닫힌#3449가 이미 first-class Docker/Compose를 받았고, 그 위에 이 이슈는 “배포 산출물을 레지스트리에 올린다”는 다음 층이다. 2.51.0 제품 코드나 types/config 분할과는 무관하고, 지금 merge-train 우선순위보다는 릴리스 엔지니어링 백로그에 가깝다.경로
Dockerfile(HEAD) - 이미 존재하며 제안 초안보다 제약(호환성 검증, 시드 hub config, 이중 홈, Bun 1.4.2 pin)이 많다. 초안으로 교체하면 안 된다.경로
compose.yaml/docker//.dockerignore- 공식 소스빌드 경로가 이미 있다. 이슈 체크리스트 1번(Dockerfile 추가)은 done에 가깝고, 남은 건 publish다.경로
docs-site/src/content/docs/guides/remote-hub.md- “opencodex does not publish an official container image”가 현재 정책을 명시한다. GHCR을 켜면 이 문장과 사용법(pull vs build)을 같이 바꿔야 한다.경로
.github/workflows/release.yml- npm 게이트만 있다. 컨테이너 publish를 같은 릴리스 SHA에 묶을지, 별도docker-publish.yml로 둘지 설계가 비어 있다.경로 제안
HEALTHCHECK/CMD- HEAD와 방향은 비슷하나, 공식 이미지는 토큰 부트스트랩·read_only·capability drop을 Compose에서 강제한다. 레지스트리 이미지도 같은 계약을 문서/스모크로 고정해야 한다.이슈 본문 “No official Dockerfile” - HEAD 기준으로 부정확하다. 제목/본문을 “publish official multi-arch image to GHCR on release”로 좁히면 리뷰·구현 범위가 맑아진다.
메인테이너의 판단이 필요한 지점
release.yml과 동일 SHA/태그에 묶을지, 수동workflow_dispatch만 허용할지.:latest갱신 규칙을 어디까지 약속할지.#4179(remote Git build context)와 한 프로그램으로 묶을지, “미리 빌드된 이미지”로#4179일부를 대체할지.너의 추천
Dockerfile+compose.yaml+docker-smoke를 입력으로 하는 publish 워크플로 PR을 받으라. 커뮤니티 초안은 참고만.wontfix/deferred로 닫아도 된다. 지금은 제품 블로커가 아니라 릴리스 정책 선택이다.#4179와 라벨/링크로 묶고, types/config 분할이나 제품 버그 드로우다운보다 뒤로 두라.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 27, 2026
Release train 4 triage (dev
24b2f39b, T4-I-4198): Keeping this open, narrowed to official image publication. The source-build Dockerfile exists, while the hub guide still says no official image is published. #6077 proposes Cloudflare Containers hosting, not GHCR/Docker Hub publication. Next: settle registry, architecture/signing and release ownership, then add a reviewed build/publish workflow.