Summary
Set up GitHub Actions release pipelines that produce versioned, ready-to-run artifacts for ShellWatch. The goal is a single Docker image for most users, a standalone tarball, and a separate release path for the Go agent client.
Artifacts
1. Docker image (primary distribution)
Registry: GitHub Container Registry (ghcr.io/rado0x54/shellwatch)
Tag strategy:
| Trigger |
Image tags |
| Push to `develop` |
`:develop`, `:sha-` |
| Push to `main` |
`:stable`, `:sha-` |
| Push tag `v1.2.3` |
`:1.2.3`, `:1.2`, `:latest` (retag from SHA, no rebuild) |
Architecture: `linux/amd64` only (for now).
Dockerfile — multi-stage build
Three stages to keep the runtime image minimal:
Stage 1 — `deps` (node:24-bookworm)
- Install build tools: `python3`, `make`, `g++`, `libssl-dev`
- Copy `package.json`, `pnpm-lock.yaml`, `pnpm-workspace.yaml`
- Run `pnpm install --frozen-lockfile` (compiles native addons: better-sqlite3, ssh2, cbor-extract, cpu-features)
Stage 2 — `build` (same base)
- Copy source (`src/`, `client/`, `tsconfig.json`)
- Run `pnpm build` (tsc + SvelteKit static adapter → `dist/`)
- Run `pnpm prune --prod` to drop devDependencies
Stage 3 — `runtime` (node:24-bookworm-slim)
- Copy from build: `dist/`, `node_modules/`, `drizzle/`, `package.json`
- Create non-root user (`shellwatch`)
- Create volume mount points: `/app/data`, `/app/keys`
- Expose port 3000
- Entrypoint: `node dist/index.js`
Both `deps` and `runtime` stages must use the same Debian release (bookworm) for native addon binary compatibility.
Known considerations:
- ssh2 GitHub fork: `pnpm install` clones `rado0x54/ssh2#feat/webauthn-sk-ecdsa` during build. If this fork is ever made private, the Docker build will need a GitHub token via `--mount=type=secret`.
- Drizzle migrations: `dist/db/migrate.js` resolves `../../drizzle` relative to itself, so the `drizzle/` directory must be copied to `/app/drizzle/` in the runtime image.
- Static files: Fastify serves from `/dist/client`, which works with WORKDIR `/app`.
.dockerignore
Exclude: `node_modules`, `dist`, `data`, `keys`, `logs`, `coverage`, `.git`, `.github`, `.md`, `.env`, `config.yaml`, `.husky`, `agent-client`
Volumes:
- `/app/data` — SQLite database (persisted)
- `/app/keys` — SSH private keys (mounted from host or secret)
- `/app/config.yaml` — bind mount
Runtime usage:
```bash
docker run -d
-v ./config.yaml:/app/config.yaml:ro
-v ./data:/app/data
-v ./keys:/app/keys:ro
-p 3000:3000
ghcr.io/rado0x54/shellwatch:latest
```
Users must mount a `config.yaml` — the image does not ship one. The app exits on startup without it (existing behavior).
2. docker-compose.yml
Provided in the repo for quick starts:
```yaml
services:
shellwatch:
image: ghcr.io/rado0x54/shellwatch:latest
ports:
- "3000:3000"
volumes:
- ./data:/app/data
- ./keys:/app/keys
- ./config.yaml:/app/config.yaml:ro
environment:
- HOST=0.0.0.0
restart: unless-stopped
```
3. Standalone tarball
For users who don't want Docker:
- `shellwatch--linux-x64.tar.gz`
- Contains: `dist/`, `drizzle/`, `package.json`, `pnpm-lock.yaml`, `config.sample.yaml`
- User runs: `pnpm install --prod && node dist/index.js`
Release pipelines (GitHub Actions)
CI reuse
Add `workflow_call:` to `.github/workflows/ci.yml`'s `on:` block (one-line, non-breaking addition) so the release workflow can call CI as a prerequisite.
ShellWatch release workflow (`.github/workflows/release.yml`)
Triggers:
```yaml
on:
push:
branches: [develop, main]
tags: ["v*"]
```
Jobs:
- `ci` — calls `.github/workflows/ci.yml` (reusable workflow). All lint/typecheck/test jobs must pass.
- `docker` — `needs: ci`, two paths:
- Branch push (develop/main): Full Docker build+push using `docker/build-push-action` with GitHub Actions layer cache (`type=gha`). Tags: `:develop`/`:stable` + `:sha-`.
- Tag push (v):* No rebuild — retag the existing `:sha-` image as `:X.Y.Z`, `:X.Y`, `:latest` using `crane` (`imjasonh/setup-crane`).
- `release` (tag pushes only) — create GitHub Release:
- Attach standalone tarball
- SHA256 checksums
- Auto-generated changelog via `generate_release_notes: true`
- Commit updated `CHANGELOG.md` back to `main`
Agent release workflow (`.github/workflows/agent-release.yml`)
Triggers:
```yaml
on:
push:
tags: ["agent/v*"]
```
Jobs:
- Go cross-compile — build `agent-client/` for linux/darwin × amd64/arm64
- GitHub Release — create release with:
- Attached cross-platform binaries
- SHA256 checksums
- Auto-generated changelog via `generate_release_notes: true`
- Commit updated `agent-client/CHANGELOG.md` back to `main`
Changelog
Two separate changelogs, both auto-generated and committed back by their respective release workflows:
- `CHANGELOG.md` — ShellWatch app (updated on `v*` tags, committed to `main`)
- `agent-client/CHANGELOG.md` — Go agent (updated on `agent/v*` tags, committed to `main`)
Uses GitHub's automatically generated release notes.
Versioning
Semver. Tag-driven — no version in package.json (or synced via release workflow).
Deliverables
Related
Summary
Set up GitHub Actions release pipelines that produce versioned, ready-to-run artifacts for ShellWatch. The goal is a single Docker image for most users, a standalone tarball, and a separate release path for the Go agent client.
Artifacts
1. Docker image (primary distribution)
Registry: GitHub Container Registry (
ghcr.io/rado0x54/shellwatch)Tag strategy:
Architecture: `linux/amd64` only (for now).
Dockerfile — multi-stage build
Three stages to keep the runtime image minimal:
Stage 1 — `deps` (node:24-bookworm)
Stage 2 — `build` (same base)
Stage 3 — `runtime` (node:24-bookworm-slim)
Both `deps` and `runtime` stages must use the same Debian release (bookworm) for native addon binary compatibility.
Known considerations:
.dockerignore
Exclude: `node_modules`, `dist`, `data`, `keys`, `logs`, `coverage`, `.git`, `.github`, `.md`, `.env`, `config.yaml`, `.husky`, `agent-client`
Volumes:
Runtime usage:
```bash
docker run -d
-v ./config.yaml:/app/config.yaml:ro
-v ./data:/app/data
-v ./keys:/app/keys:ro
-p 3000:3000
ghcr.io/rado0x54/shellwatch:latest
```
Users must mount a `config.yaml` — the image does not ship one. The app exits on startup without it (existing behavior).
2. docker-compose.yml
Provided in the repo for quick starts:
```yaml
services:
shellwatch:
image: ghcr.io/rado0x54/shellwatch:latest
ports:
- "3000:3000"
volumes:
- ./data:/app/data
- ./keys:/app/keys
- ./config.yaml:/app/config.yaml:ro
environment:
- HOST=0.0.0.0
restart: unless-stopped
```
3. Standalone tarball
For users who don't want Docker:
Release pipelines (GitHub Actions)
CI reuse
Add `workflow_call:` to `.github/workflows/ci.yml`'s `on:` block (one-line, non-breaking addition) so the release workflow can call CI as a prerequisite.
ShellWatch release workflow (`.github/workflows/release.yml`)
Triggers:
```yaml
on:
push:
branches: [develop, main]
tags: ["v*"]
```
Jobs:
Agent release workflow (`.github/workflows/agent-release.yml`)
Triggers:
```yaml
on:
push:
tags: ["agent/v*"]
```
Jobs:
Changelog
Two separate changelogs, both auto-generated and committed back by their respective release workflows:
Uses GitHub's automatically generated release notes.
Versioning
Semver. Tag-driven — no version in package.json (or synced via release workflow).
Deliverables
Related