Skip to content

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

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

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 packaging

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions