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
59 changes: 3 additions & 56 deletions .github/workflows/web-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Deploy web app
name: Build web app
on:
workflow_dispatch:
inputs:
tag:
description: "Git tag / registry tag (e.g. 1.0.2). Image is built and pushed every run."
description: "Git tag / registry tag (e.g. 1.0.2). Image is built and pushed
every run."
required: true
type: string

Expand Down Expand Up @@ -68,57 +69,3 @@ jobs:
${{ env.IMAGE }}:${{ steps.meta.outputs.tag }}
${{ env.IMAGE }}:sha-${{ steps.git.outputs.short }}
${{ env.IMAGE }}:latest

deploy:
name: Deploy to host
needs: build-package
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Ensure remote directory
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
timeout: 2m
script: mkdir -p ~/pengine

- name: Copy docker-compose.yml to host
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
timeout: 2m
source: deployment/docker-compose.yml
target: ~/pengine/
overwrite: true
strip_components: 1

- name: SSH — docker login, compose pull, up
uses: appleboy/ssh-action@v1.2.5
env:
GHCR_USER: ${{ github.repository_owner }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PENGINE_WEB_IMAGE: "${{ needs.build-package.outputs.image }}:${{ needs.build-package.outputs.tag }}"
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
port: 22
timeout: 3m
command_timeout: 10m
envs: GHCR_USER,GHCR_TOKEN,PENGINE_WEB_IMAGE
script: |
set -euo pipefail
cd ~/pengine/deployment
echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin
export PENGINE_WEB_IMAGE
docker compose pull
docker compose up -d
66 changes: 66 additions & 0 deletions deployment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Pengine deployment

## Production (same host as Pengui)

**Use the Pengui stack** — Pengine is an optional Compose **profile** there, so it shares **`pengui-network`** with nginx (no second compose project, no `external` network).

1. In the Pengui repo, set **`PENGINE_ENABLE=1`** and **`PENGINE_WEB_IMAGE`** (e.g. `ghcr.io/pengine-ai/pengine-web:1.0.1`) in **GitHub Actions variables** or in `~/pengui/deployment/.env`.
2. Deploy Pengui (`deploy.sh` or CI). That runs `docker compose --profile pengine up -d pengine-web`.
3. Nginx proxies to **`http://pengine-app:1422`** (see Pengui `nginx/templates`).

**Do not** run this directory’s `docker compose up` on the same server at the same time — you would get a duplicate **`pengine-app`** name. Remove any old standalone Pengine stack first: `docker rm -f pengine-app` (only if moving to Pengui profile).

## Local / standalone (this repo only)

```bash
cd deployment
docker compose up -d
curl -fsS http://127.0.0.1:1422/ | head
```

## TLS for `pengine.net`

Configure **`PENGINE_SUBDOMAIN`** on the **Pengui** repo (Certbot + nginx vhost); see Pengui `deployment/README.md`.

## Remove the container and pull a fresh image

Use this after a new image tag is published, if the container is stuck, or you want to clear the cached local image.

### Production (Pengui stack, profile `pengine`)

Run on the server:

```bash
cd ~/pengui/deployment

docker compose --profile pengine stop pengine-web
docker compose rm -f pengine-web

# If a stray container exists outside compose:
docker rm -f pengine-app 2>/dev/null || true

# Optional: remove cached images so the next pull is guaranteed fresh
for id in $(docker images 'ghcr.io/pengine-ai/pengine-web' -q); do docker rmi -f "$id"; done 2>/dev/null || true

docker compose pull pengine-web
docker compose --profile pengine up -d pengine-web
```

Private images require **`docker login ghcr.io`** (PAT with `read:packages`) first.

### Local / standalone (this repo’s `deployment/docker-compose.yml`)

```bash
cd deployment

docker compose down
docker rmi ghcr.io/pengine-ai/pengine-web:latest 2>/dev/null || true # adjust tag if needed

docker compose pull
docker compose up -d
```

## Troubleshooting

- **`network … external … not found`**: use **Pengui + `--profile pengine`**, not a separate compose with `external: pengui-network`.
- **`incorrect label com.docker.compose.network`**: never run `docker network create pengui-network` by hand; let Pengui’s `docker compose up` create the network.
10 changes: 7 additions & 3 deletions deployment/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Local / standalone: Pengine only (default bridge, port 1422 on host).
#
# Production next to Pengui: use Pengui’s docker-compose with --profile pengine
# (see pengui repo deployment/docker-compose.yml) — same network as nginx, no external network.

services:
pengine-web:
image: ${PENGINE_WEB_IMAGE:-ghcr.io/pengine-ai/pengine-web:latest}
container_name: pengine-app
hostname: pengine
restart: unless-stopped
# Vite preview — serves `dist/` (must match deploy/Dockerfile CMD).
command: ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "1422"]
expose:
- "1422"
ports:
- "1422:1422"
environment:
- NODE_ENV=production
healthcheck:
Expand Down
9 changes: 9 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export default defineConfig(async () => {
preview: {
port: 1422,
strictPort: true,
// Required when nginx (or any reverse proxy) sends Host: pengine.net — Vite 7 blocks unknown hosts by default.
allowedHosts: [
"pengine.net",
"localhost",
"127.0.0.1",
...(process.env.VITE_PREVIEW_ALLOWED_HOSTS?.split(",")
.map((h) => h.trim())
.filter(Boolean) ?? []),
],
},
};
});