Skip to content

[Feature]: Official multi-arch Docker image and automated build workflow (GHCR / Docker Hub) #4198

Open

Description

@nordz0r

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?

  1. 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.
  2. 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.
  3. 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?

  1. 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).
  2. 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

  1. 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.
  2. 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

  • 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

  1. lidge-jun commented on Sep 10, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 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 하는 편이 신뢰·지연·중복 빌드 면에서 낫다는 주장이다.

    다만 현재 dev HEAD 6d3ad12e3에는 이미 공식 소스빌드 컨테이너 스택이 있다. 루트 Dockerfile은 multi-stage + digest-pinned oven/bun:1.4.2, GUI 빌드, non-root bun, /healthz HEALTHCHECK, 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.md Docker 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”로 좁히면 리뷰·구현 범위가 맑아진다.

    메인테이너의 판단이 필요한 지점

    • 공식 게시 이미지 정책을 바꿀지(가이드의 “does not publish”를 폐기할지), 당분간 소스빌드+커뮤니티 이미지를 유지할지.
    • 게시를 npm release.yml과 동일 SHA/태그에 묶을지, 수동 workflow_dispatch만 허용할지.
    • GHCR만 할지 Docker Hub도 할지. 서명(cosign 등)과 :latest 갱신 규칙을 어디까지 약속할지.
    • #4179(remote Git build context)와 한 프로그램으로 묶을지, “미리 빌드된 이미지”로 #4179 일부를 대체할지.

    너의 추천

    • 닫지 말고 범위를 좁혀 유지하라. Dockerfile 신설 요청은 HEAD에 이미 있으므로 중복에 가깝고, 남는 가치는 GHCR(필요 시 Hub) 자동 publish다.
    • 제안 Dockerfile/워크플로를 그대로 PR로 받지 말고, 기존 Dockerfile+compose.yaml+docker-smoke를 입력으로 하는 publish 워크플로 PR을 받으라. 커뮤니티 초안은 참고만.
    • 메인테이너가 “아직 공식 이미지를 안 올린다”로 결정하면 이슈에 그 정책을 남기고 wontfix/deferred로 닫아도 된다. 지금은 제품 블로커가 아니라 릴리스 정책 선택이다.
    • #4179와 라벨/링크로 묶고, types/config 분할이나 제품 버그 드로우다운보다 뒤로 두라.

    이 댓글은 grok-bot이 작성했습니다

  2. added
    priority: P3Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro
    on Sep 24, 2026
  3. lidge-jun commented on Sep 27, 2026

    @lidge-jun
    Owner

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestinstallInstallation or packagingpriority: P3Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions