From ec9da1da9756430ade50e92686ee54ba04f974e0 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Mon, 8 Jun 2026 21:39:59 -0700 Subject: [PATCH] Publish self-host Docker image --- .github/workflows/ci.yml | 17 +++ .../workflows/publish-executor-package.yml | 5 + .github/workflows/publish-selfhost-docker.yml | 107 ++++++++++++++++++ RELEASING.md | 17 ++- apps/cli/release-notes/next.md | 4 + apps/host-selfhost/Dockerfile | 3 + apps/host-selfhost/README.md | 12 ++ docs/self-hosting/guide.mdx | 43 ++++++- 8 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/publish-selfhost-docker.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 879d1696c..55076666c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,3 +98,20 @@ jobs: - name: Build Electron main/preload/renderer run: bunx --bun electron-vite build working-directory: apps/desktop + + selfhost-docker-smoke: + name: Self-host Docker image + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build self-host image + uses: docker/build-push-action@v6 + with: + context: . + file: apps/host-selfhost/Dockerfile + push: false + tags: executor-selfhost:ci diff --git a/.github/workflows/publish-executor-package.yml b/.github/workflows/publish-executor-package.yml index 2424e4215..113ed9411 100644 --- a/.github/workflows/publish-executor-package.yml +++ b/.github/workflows/publish-executor-package.yml @@ -82,3 +82,8 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: gh workflow run publish-desktop.yml -f tag="$RELEASE_TAG" + + - name: Trigger self-host Docker publish + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run publish-selfhost-docker.yml -f tag="$RELEASE_TAG" diff --git a/.github/workflows/publish-selfhost-docker.yml b/.github/workflows/publish-selfhost-docker.yml new file mode 100644 index 000000000..ac54fb799 --- /dev/null +++ b/.github/workflows/publish-selfhost-docker.yml @@ -0,0 +1,107 @@ +name: Publish Self-host Docker Image +run-name: "${{ format('publish self-host docker {0}', inputs.tag) }}" + +on: + workflow_dispatch: + inputs: + tag: + description: Git tag to publish (e.g. v1.5.0) + required: true + type: string + +permissions: + contents: read + +concurrency: + group: publish-selfhost-docker-${{ inputs.tag }} + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.11 + + - name: Validate release tag + env: + RAW_RELEASE_TAG: ${{ inputs.tag }} + run: bun run scripts/validate-release-ref.ts --tag-env RAW_RELEASE_TAG --write-env RELEASE_TAG + + - name: Checkout release tag + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT || github.token }} + run: | + auth_remote="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git fetch --force --tags "$auth_remote" "refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG" + git checkout --detach "$RELEASE_TAG" + + - name: Resolve image metadata + id: image + shell: bash + run: | + set -euo pipefail + + version="${RELEASE_TAG#v}" + owner="$(printf '%s' "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')" + image="ghcr.io/${owner}/executor-selfhost" + revision="$(git rev-parse HEAD)" + + if [[ "$version" == *-* ]]; then + channel="beta" + else + channel="latest" + fi + + { + echo "image=$image" + echo "version=$version" + echo "channel=$channel" + echo "tags<> "$GITHUB_OUTPUT" + + - 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 GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push self-host image + uses: docker/build-push-action@v6 + with: + context: . + file: apps/host-selfhost/Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.image.outputs.tags }} + labels: ${{ steps.image.outputs.labels }} diff --git a/RELEASING.md b/RELEASING.md index e166f36a0..19a4ec1b1 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,8 +1,9 @@ # Releasing -This repo uses Changesets for version orchestration and two publish paths: -the CLI (`executor` npm package plus its platform packages) and the -`@executor-js/*` library packages (`core`, `sdk`, and the public plugins). +This repo uses Changesets for version orchestration and three publish paths: +the CLI (`executor` npm package plus its platform packages), the +`@executor-js/*` library packages (`core`, `sdk`, and the public plugins), and +the self-host Docker image. ## Normal release flow @@ -21,6 +22,12 @@ the CLI (`executor` npm package plus its platform packages) and the - performs a full dry-run release build before publish - publishes the CLI npm package under the correct dist-tag - creates or updates the GitHub release with build artifacts + - dispatches `.github/workflows/publish-desktop.yml` + - dispatches `.github/workflows/publish-selfhost-docker.yml` +6. The self-host Docker workflow publishes `ghcr.io/rhyssullivan/executor-selfhost` + for `linux/amd64` and `linux/arm64`: + - stable releases get `vX.Y.Z`, `X.Y.Z`, and `latest` + - prereleases get `vX.Y.Z-...`, `X.Y.Z-...`, and `beta` ## Beta releases @@ -52,6 +59,10 @@ To pack the `@executor-js/*` library packages without publishing: - `bun run release:publish:packages:dry-run` +To validate the self-host Dockerfile locally without publishing: + +- `docker build -f apps/host-selfhost/Dockerfile -t executor-selfhost:local .` + ## Release notes User-facing release notes live at `apps/cli/release-notes/next.md` — diff --git a/apps/cli/release-notes/next.md b/apps/cli/release-notes/next.md index f0d91fd1c..d8c232325 100644 --- a/apps/cli/release-notes/next.md +++ b/apps/cli/release-notes/next.md @@ -7,6 +7,10 @@ - OAuth Dynamic Client Registration data is reused across retries and reconnects, including scopes, so providers are not asked to register duplicate clients. - MCP tool output schemas now match the actual invocation result envelope, including `content`, `structuredContent`, `_meta`, and `isError`. +### Self-hosted Docker image + +- Self-hosted Executor now publishes a multi-architecture GHCR image at `ghcr.io/rhyssullivan/executor-selfhost`, with stable releases tagged as `latest` and prereleases tagged as `beta`. + ## UI - No UI-only changes in this patch. diff --git a/apps/host-selfhost/Dockerfile b/apps/host-selfhost/Dockerfile index 2d24328bd..18b3c4f01 100644 --- a/apps/host-selfhost/Dockerfile +++ b/apps/host-selfhost/Dockerfile @@ -32,6 +32,9 @@ RUN rm -rf node_modules && bun install --frozen-lockfile --production --ignore-s # ── Runtime stage: serve the built app under Bun ──────────────────────────── FROM oven/bun:1 AS runtime WORKDIR /app +LABEL org.opencontainers.image.source="https://github.com/RhysSullivan/executor" \ + org.opencontainers.image.description="Single-container self-hosted Executor" \ + org.opencontainers.image.licenses="MIT" ENV NODE_ENV=production \ EXECUTOR_HOST=0.0.0.0 \ PORT=4788 \ diff --git a/apps/host-selfhost/README.md b/apps/host-selfhost/README.md index 41f997cd8..7969d27f5 100644 --- a/apps/host-selfhost/README.md +++ b/apps/host-selfhost/README.md @@ -7,6 +7,18 @@ external database, worker, or proxy. ## Run it +Using the published image: + +```bash +docker run -d \ + --name executor-selfhost \ + -p 4788:4788 \ + -v executor-data:/data \ + ghcr.io/rhyssullivan/executor-selfhost:latest +``` + +Or build from a repository clone: + ```bash # From this directory: docker compose up -d --build diff --git a/docs/self-hosting/guide.mdx b/docs/self-hosting/guide.mdx index b114a986b..35bf9d3d0 100644 --- a/docs/self-hosting/guide.mdx +++ b/docs/self-hosting/guide.mdx @@ -5,22 +5,39 @@ description: Run Executor on your own infrastructure in a single container. Executor self-hosts as **one container** — the database (SQLite/libSQL), the QuickJS code sandbox, and the MCP server all run in-process. There is no separate -database, worker, or proxy to operate, and no required configuration: a bare -`docker compose up` boots a working instance and walks you through creating the -admin account in the browser. +database, worker, or proxy to operate, and no required configuration: a published +image or a bare `docker compose up` boots a working instance and walks you +through creating the admin account in the browser. ## Quick start -From a clone of the repository: +Using the published GHCR image: ```bash -cd apps/host-selfhost -docker compose up -d --build +docker run -d \ + --name executor-selfhost \ + -p 4788:4788 \ + -v executor-data:/data \ + ghcr.io/rhyssullivan/executor-selfhost:latest ``` Then open [http://localhost:4788](http://localhost:4788). On a fresh instance you'll see a **setup screen** — create the first admin account, and you're in. +Published images are tagged as: + +- `ghcr.io/rhyssullivan/executor-selfhost:latest` for the latest stable release +- `ghcr.io/rhyssullivan/executor-selfhost:beta` for the latest prerelease +- `ghcr.io/rhyssullivan/executor-selfhost:vX.Y.Z` for a pinned release tag +- `ghcr.io/rhyssullivan/executor-selfhost:X.Y.Z` for the same pinned version without `v` + +From a clone of the repository: + +```bash +cd apps/host-selfhost +docker compose up -d --build +``` + That's the whole install. The container persists its data (database and generated keys) in the `executor-data` volume, so it survives restarts and upgrades. @@ -107,6 +124,20 @@ keys as well as your data. ## Upgrading +If you use the published image: + +```bash +docker pull ghcr.io/rhyssullivan/executor-selfhost:latest +docker rm -f executor-selfhost +docker run -d \ + --name executor-selfhost \ + -p 4788:4788 \ + -v executor-data:/data \ + ghcr.io/rhyssullivan/executor-selfhost:latest +``` + +If you build from source: + ```bash cd apps/host-selfhost git pull