-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add Docker support and deployment workflow #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Used for `docker build -f deploy/Dockerfile .` (context is repo root). | ||
| .git | ||
| .github | ||
| node_modules | ||
| dist | ||
| dist-ssr | ||
| src-tauri | ||
| test-results | ||
| playwright-report | ||
| e2e | ||
| doc | ||
| .claude | ||
| *.md | ||
| !README.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Used by `vite build` — public site URL (DNS + TLS terminate at the edge). | ||
| VITE_APP_ORIGIN=https://pengine.net |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| name: Deploy web app | ||
|
|
||
| # Build the pengine web image, publish to GHCR, and roll it out on the deploy | ||
| # host via docker compose under ~/pengine. This workflow only updates the app | ||
| # container (static bundle on :80 → 127.0.0.1:1420). Reverse-proxy / nginx / | ||
| # TLS for the public site is configured elsewhere (another repository). | ||
| # | ||
| # Triggers: | ||
| # - Push a tag matching v* → builds and deploys that tag | ||
| # - Manual dispatch → pick a tag to redeploy | ||
| # | ||
| # Required repo secrets: | ||
| # DEPLOY_HOST — hostname or IP of the deploy target | ||
| # DEPLOY_USER — SSH user on the deploy target | ||
| # DEPLOY_SSH_KEY — private SSH key (PEM) authorized on the target | ||
| # Optional: | ||
| # DEPLOY_HOST_KNOWN_HOSTS — verified known_hosts line(s); if unset, CI uses ssh-keyscan | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - "v*" | ||
| workflow_dispatch: | ||
| inputs: | ||
| tag: | ||
| description: "Release tag to deploy (e.g. v1.0.1)" | ||
| required: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: write | ||
|
|
||
| env: | ||
| IMAGE: ghcr.io/${{ github.repository_owner }}/pengine-web | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| image_ref: ${{ steps.ref.outputs.image_ref }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| # Tag push: github.ref; manual: workflow input (otherwise default branch would build) | ||
| ref: ${{ github.event.inputs.tag || github.ref }} | ||
|
|
||
| - uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - uses: docker/login-action@v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.repository_owner }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Resolve version | ||
| id: ver | ||
| run: | | ||
| TAG="${{ github.event.inputs.tag || github.ref_name }}" | ||
| VERSION="${TAG#v}" | ||
| echo "tag=$TAG" >>"$GITHUB_OUTPUT" | ||
| echo "version=$VERSION" >>"$GITHUB_OUTPUT" | ||
|
|
||
| - name: Build and push | ||
| id: build | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: deploy/Dockerfile | ||
| platforms: linux/amd64 | ||
| push: true | ||
| provenance: false | ||
| sbom: false | ||
| build-args: | | ||
| VITE_APP_ORIGIN=https://pengine.net | ||
| tags: | | ||
| ${{ env.IMAGE }}:${{ steps.ver.outputs.version }} | ||
| ${{ env.IMAGE }}:latest | ||
|
|
||
| - name: Export image ref | ||
| id: ref | ||
| run: echo "image_ref=${{ env.IMAGE }}@${{ steps.build.outputs.digest }}" >>"$GITHUB_OUTPUT" | ||
|
|
||
| deploy: | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Start SSH agent | ||
| uses: webfactory/ssh-agent@v0.9.0 | ||
| with: | ||
| ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }} | ||
|
|
||
| - name: Trust host key | ||
| env: | ||
| DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} | ||
| DEPLOY_HOST_KNOWN_HOSTS: ${{ secrets.DEPLOY_HOST_KNOWN_HOSTS }} | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p ~/.ssh | ||
| chmod 700 ~/.ssh | ||
| if [ -n "${DEPLOY_HOST_KNOWN_HOSTS:-}" ]; then | ||
| printf '%s\n' "$DEPLOY_HOST_KNOWN_HOSTS" >>~/.ssh/known_hosts | ||
| else | ||
| ssh-keyscan -H "$DEPLOY_HOST" >>~/.ssh/known_hosts 2>/dev/null \ | ||
| || { echo "ssh-keyscan failed; set optional DEPLOY_HOST_KNOWN_HOSTS to pin the host key." >&2; exit 1; } | ||
| fi | ||
| chmod 644 ~/.ssh/known_hosts | ||
|
|
||
| - name: Copy docker-compose.yml to host | ||
| env: | ||
| DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} | ||
| DEPLOY_USER: ${{ secrets.DEPLOY_USER }} | ||
| run: | | ||
| ssh "$DEPLOY_USER@$DEPLOY_HOST" 'mkdir -p ~/pengine' | ||
| scp deploy/docker-compose.yml "$DEPLOY_USER@$DEPLOY_HOST:~/pengine/docker-compose.yml" | ||
|
|
||
| - name: Pull and restart | ||
| env: | ||
| DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} | ||
| DEPLOY_USER: ${{ secrets.DEPLOY_USER }} | ||
| GHCR_USER: ${{ github.repository_owner }} | ||
| GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| IMAGE_REF: ${{ needs.build.outputs.image_ref }} | ||
| run: | | ||
| ssh "$DEPLOY_USER@$DEPLOY_HOST" \ | ||
| GHCR_USER="$GHCR_USER" \ | ||
| GHCR_TOKEN="$GHCR_TOKEN" \ | ||
| PENGINE_WEB_IMAGE="$IMAGE_REF" \ | ||
| 'bash -s' <<'REMOTE' | ||
| set -euo pipefail | ||
| cd ~/pengine | ||
| echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin | ||
| export PENGINE_WEB_IMAGE | ||
| docker compose pull | ||
| docker compose up -d --remove-orphans | ||
| docker image prune -f | ||
| REMOTE | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # pengine-web: **website only** — same static bundle the Tauri desktop shell loads from | ||
| # `dist/`, without building or running the native app. | ||
| # | ||
| # Mirrors `src-tauri/tauri.conf.json`: | ||
| # - `build.beforeBuildCommand` → `bun run build` (→ `build:web`) | ||
| # - `build.frontendDist` → `../dist` (this image serves that folder on :80) | ||
| # | ||
| # Intentionally omitted: `tauri build`, Cargo, `src-tauri/` (see /.dockerignore). | ||
| # | ||
| # Build: docker build -f deploy/Dockerfile . | ||
| # Context: repository root (see /.dockerignore). | ||
|
|
||
| FROM oven/bun:1 AS build | ||
| WORKDIR /app | ||
|
|
||
| ENV HUSKY=0 | ||
| ENV NODE_ENV=production | ||
|
|
||
| COPY package.json bun.lock ./ | ||
| RUN bun install --frozen-lockfile --ignore-scripts | ||
|
|
||
| COPY . . | ||
|
|
||
| ARG VITE_APP_ORIGIN=https://pengine.net | ||
| ENV VITE_APP_ORIGIN=${VITE_APP_ORIGIN} | ||
|
|
||
| # Web UI only — matches Tauri’s packaged frontend; does not invoke the Tauri CLI. | ||
| RUN bun run build:web | ||
|
|
||
| FROM ghcr.io/static-web-server/static-web-server:2-alpine | ||
| USER root | ||
| RUN apk add --no-cache wget | ||
| USER sws | ||
|
|
||
| COPY --chown=sws:sws --from=build /app/dist /home/sws/public | ||
|
|
||
| ENV SERVER_HOST=0.0.0.0 | ||
| ENV SERVER_PORT=80 | ||
| ENV SERVER_ROOT=/home/sws/public | ||
| ENV SERVER_FALLBACK_PAGE=/home/sws/public/index.html | ||
| ENV SERVER_LOG_LEVEL=error | ||
|
|
||
| EXPOSE 80 | ||
|
|
||
| HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ | ||
| CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Runs the pengine web image on the deploy host (plain HTTP on :80 in-container, | ||
| # published to 127.0.0.1:1420). Reverse-proxy / TLS is not part of this repo — | ||
| # configure that in your ops repo (e.g. nginx → http://127.0.0.1:1420). | ||
| # | ||
| # PENGINE_WEB_IMAGE is set by the GitHub Actions deploy step to the image | ||
| # reference being rolled out (e.g. ghcr.io/pengine-ai/pengine-web:v1.0.1). | ||
| # Docker Compose substitutes it from the environment at `up` time. | ||
|
|
||
| services: | ||
| web: | ||
| image: ${PENGINE_WEB_IMAGE:?PENGINE_WEB_IMAGE is required} | ||
| container_name: pengine | ||
| restart: unless-stopped | ||
| ports: | ||
| - "127.0.0.1:1420:80" | ||
| healthcheck: | ||
| test: ["CMD", "wget", "-qO-", "http://127.0.0.1/"] | ||
| interval: 30s | ||
| timeout: 3s | ||
| retries: 3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Deploying the web app | ||
|
|
||
| The public production URL for the web UI is **`https://pengine.net`** (DNS A/AAAA → your host; TLS at the reverse proxy). The Vite production build embeds this via [`VITE_APP_ORIGIN`](../../.env.production) for client-side metadata. | ||
|
|
||
| The pengine web bundle is deployed to a remote host via the | ||
| [`Deploy web app`](../../.github/workflows/web-deploy.yml) GitHub Actions | ||
| workflow. Each run: | ||
|
|
||
| 1. Builds [`deploy/Dockerfile`](../../deploy/Dockerfile) from the **repo root** | ||
| (see [`/.dockerignore`](../../.dockerignore)): `bun install` → [`bun run build:web`](../../package.json) | ||
| (same as Tauri [`build.beforeBuildCommand`](../../src-tauri/tauri.conf.json) / [`build.frontendDist`](../../src-tauri/tauri.conf.json); **no** `tauri build` or Rust) → [static-web-server](https://github.com/static-web-server/static-web-server) | ||
| with **`SERVER_FALLBACK_PAGE`** so React Router paths resolve. CI passes | ||
| `VITE_APP_ORIGIN=https://pengine.net` as a **build-arg** (overridable locally). | ||
| 2. Pushes the image to GHCR as `ghcr.io/<owner>/pengine-web:<version>` and | ||
| `:latest`. | ||
| 3. SSHes into the deploy host, copies | ||
| [`deploy/docker-compose.yml`](../../deploy/docker-compose.yml) to `~/pengine`, | ||
| logs in to GHCR, then `docker compose pull && docker compose up -d`. | ||
|
|
||
| The container publishes on `127.0.0.1:1420`. **TLS and reverse-proxy (e.g. nginx) | ||
| for the public site are not defined in this repository** — maintain that in | ||
| your ops / infrastructure repo and point it at `http://127.0.0.1:1420` (or | ||
| adjust the published port in `docker-compose.yml` to match your layout). | ||
|
|
||
| ## Triggers | ||
|
|
||
| - **Tag push** — pushing a tag matching `v*` (e.g. `v1.0.1`) deploys that tag. | ||
| The same tag also fires [`App Release`](../../.github/workflows/app-release.yml); | ||
| the two run in parallel. | ||
| - **Manual dispatch** — from the Actions tab, pick any existing tag to | ||
| redeploy it. | ||
|
|
||
| ## Required secrets | ||
|
|
||
| Add under *Settings → Secrets and variables → Actions*: | ||
|
|
||
| | Secret | Value | | ||
| | --- | --- | | ||
| | `DEPLOY_HOST` | Hostname or IP of the deploy target | | ||
| | `DEPLOY_USER` | SSH user on the target (must be in the `docker` group) | | ||
| | `DEPLOY_SSH_KEY` | Private SSH key (PEM, including `-----BEGIN`/`-----END` lines), with its public half in the target user's `~/.ssh/authorized_keys` | | ||
|
|
||
| Optional: | ||
|
|
||
| | Secret | Value | | ||
| | --- | --- | | ||
| | `DEPLOY_HOST_KNOWN_HOSTS` | One or more `known_hosts` lines for `DEPLOY_HOST` (paste output of a **verified** `ssh-keyscan`). If omitted, the workflow runs `ssh-keyscan` at deploy time instead. | | ||
|
|
||
| GHCR auth on the host uses the per-run `GITHUB_TOKEN` — no extra secret | ||
| needed, but the host must be able to reach `ghcr.io` on 443. | ||
|
|
||
| ## One-time host bootstrap | ||
|
|
||
| On the deploy host, as `DEPLOY_USER`: | ||
|
|
||
| ```bash | ||
| # Docker + compose plugin (Debian/Ubuntu shown; adapt for your distro). | ||
| curl -fsSL https://get.docker.com | sh | ||
| sudo usermod -aG docker "$USER" | ||
| # Log out and back in so the group takes effect. | ||
|
|
||
| # Directory the workflow drops docker-compose.yml into. | ||
| mkdir -p ~/pengine | ||
| ``` | ||
|
|
||
| Configure your external reverse-proxy (from your other repository) to forward | ||
| HTTPS traffic to `http://127.0.0.1:1420` — that is the contract between host | ||
| ingress and this deployment. | ||
|
|
||
| ## Verifying a deploy | ||
|
|
||
| After the workflow succeeds: | ||
|
|
||
| ```bash | ||
| ssh "$DEPLOY_USER@$DEPLOY_HOST" 'docker ps --filter name=pengine' | ||
| curl -fsSL https://pengine.net/ | head | ||
| ``` | ||
|
|
||
| To roll back, manually dispatch the workflow with the previous tag. | ||
|
|
||
| ## Local image build | ||
|
|
||
| ```bash | ||
| docker build -f deploy/Dockerfile --build-arg VITE_APP_ORIGIN=https://pengine.net -t pengine-web:local . | ||
| docker run --rm -p 8080:80 pengine-web:local | ||
| # Open http://127.0.0.1:8080 | ||
| ``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.