Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions .github/workflows/publish-executor-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
107 changes: 107 additions & 0 deletions .github/workflows/publish-selfhost-docker.yml
Original file line number Diff line number Diff line change
@@ -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<<TAGS"
echo "${image}:${RELEASE_TAG}"
echo "${image}:${version}"
echo "${image}:${channel}"
echo "TAGS"
echo "labels<<LABELS"
echo "org.opencontainers.image.title=Executor self-host"
echo "org.opencontainers.image.description=Single-container self-hosted Executor"
echo "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}"
echo "org.opencontainers.image.revision=${revision}"
echo "org.opencontainers.image.version=${version}"
echo "LABELS"
} >> "$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 }}
17 changes: 14 additions & 3 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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` —
Expand Down
4 changes: 4 additions & 0 deletions apps/cli/release-notes/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions apps/host-selfhost/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
12 changes: 12 additions & 0 deletions apps/host-selfhost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 37 additions & 6 deletions docs/self-hosting/guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Loading