Backend:
- update_service: registry manifest digest check (Docker Hub/ghcr/lscr/private
v2 token auth) vs local RepoDigests; in-memory cache + background loop
- port_service: parse compose ports, check /proc/net/tcp[6] + docker bindings
- template_service + bundled templates (jellyfin/vaultwarden/uptime-kuma/
paperless-ngx/gitea) with {{VAR}} placeholders; custom templates in DB
- compose_edit set_resources (deploy.resources.limits/reservations)
- routers: images, ports, templates, editor/set-resources
- Template model; background update task wired into lifespan
Frontend:
- EnvEditor (table + raw, sensitive masking, quick-insert)
- Images page + UpdateBadge + dashboard 'updates available' banner
- PortConflictDialog pre-deploy check on Deploy
- ResourcePanel (CPU/RAM sliders) as editor Limits tab
- Templates page with per-variable instantiate form
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
154 lines
6.3 KiB
Markdown
154 lines
6.3 KiB
Markdown
# StackPilot
|
|
|
|
A self-hosted Docker Compose manager for power users and homelab enthusiasts —
|
|
as intuitive as Dockge, as capable as Portainer for Compose workflows.
|
|
|
|
> **Status:** Phase 1 (Core) + Phase 2 (Volumes & GPU) + Phase 3 (Quality of
|
|
> Life) complete. Multi-host agents, backups and notifications land in Phase 4.
|
|
|
|
## What works today (Phase 1)
|
|
|
|
- **File-first stacks** — every stack is a plain `compose.yaml` (+ optional `.env`)
|
|
on disk. The DB only stores metadata; nothing is locked in.
|
|
- **Auth** — JWT access/refresh tokens, bcrypt hashing, admin/user roles, and a
|
|
first-launch setup wizard that creates the initial admin account.
|
|
- **Stack lifecycle** — create, edit, clone, delete, and `up / down / start /
|
|
stop / restart / pull / update` via `docker compose`.
|
|
- **Live status** — running / partial / stopped / error / updating, computed from
|
|
Docker container labels.
|
|
- **Real-time logs** — streamed over WebSocket, color-coded per service.
|
|
- **Monaco editor** — YAML editing with an `.env` tab and a **`docker run` →
|
|
compose** converter.
|
|
- **Dashboard** — system resource bar, stack grid with quick actions, and a
|
|
recent-activity audit feed.
|
|
- **Auto-discovery** — stacks created outside the UI (any folder under the stacks
|
|
dir containing a compose file) are picked up automatically.
|
|
- **Dark / light theme.**
|
|
|
|
### Phase 2 — Volumes & GPU
|
|
|
|
- **Volume Wizard** in the editor (right-hand helper panel): Bind / Named / NFS /
|
|
SMB-CIFS / tmpfs, with a sandboxed **host path browser** for bind mounts and a
|
|
live YAML preview. NFS/SMB `driver_opts` are generated for you.
|
|
- **GPU assignment** per service: auto-detects NVIDIA (`nvidia-smi`) and AMD/Intel
|
|
(`/dev/dri` + sysfs), injects the right YAML (NVIDIA `deploy.reservations`, or
|
|
`/dev/dri` passthrough + render/video groups + `LIBVA_DRIVER_NAME=iHD` for Intel).
|
|
- **Device passthrough**: lists host USB / serial-TTY / DRI nodes, add per service,
|
|
plus a guarded `privileged` toggle.
|
|
- All wizard edits are merged into the compose YAML **server-side** (robust,
|
|
validated) and returned to the editor for review before saving.
|
|
|
|
> GPU/device detection needs host visibility. The bundled compose bind-mounts
|
|
> `/dev:/dev:ro`; NVIDIA additionally requires the NVIDIA container runtime on the host.
|
|
|
|
### Phase 3 — Quality of Life
|
|
|
|
- **Env editor**: table mode with sensitive-value masking (`PASS`/`SECRET`/`KEY`/…
|
|
auto-detected) + raw mode, plus PUID/PGID/TZ quick-insert.
|
|
- **Image update checker**: background task compares the local manifest digest with
|
|
the registry (Docker Hub / ghcr / lscr / private v2 with token auth); update
|
|
badges on the Images page + an "updates available" banner on the dashboard.
|
|
- **Port conflict detector**: pre-deploy check against host-bound ports
|
|
(`/proc/net/tcp[6]`) and running container bindings, with a confirm dialog.
|
|
- **Resource limits**: CPU/memory sliders in the editor → `deploy.resources.limits`.
|
|
- **Template library**: bundled templates (Jellyfin, Vaultwarden, Uptime-Kuma,
|
|
Paperless-NGX, Gitea) with `{{VARIABLE}}` forms; save any stack as a custom template.
|
|
- **Healthcheck status** surfaced per container in the stack overview.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
frontend (React + Vite + Tailwind, served by nginx)
|
|
│ proxies /api and /ws
|
|
▼
|
|
backend (FastAPI + docker-py + SQLite)
|
|
│ docker-py + `docker compose` CLI
|
|
▼
|
|
Docker Engine (via /var/run/docker.sock — never exposed to the browser)
|
|
```
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
cd stackpilot
|
|
cp .env.example .env
|
|
# edit .env and set a strong SECRET_KEY: openssl rand -base64 48
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Open <http://localhost:5009> and complete the first-launch setup wizard to create
|
|
your admin account.
|
|
|
|
### Configuration
|
|
|
|
All backend settings are environment variables (see `backend/config.py`). The
|
|
most important ones:
|
|
|
|
| Variable | Default | Purpose |
|
|
|-----------------|--------------------|-------------------------------------------|
|
|
| `SECRET_KEY` | _(auto, dev only)_ | JWT signing key — **set this in prod** |
|
|
| `STACKS_DIR` | `/opt/stacks` | Where stack folders live (in-container) |
|
|
| `DATA_DIR` | `/data` | SQLite DB + app data |
|
|
| `CORS_ORIGINS` | localhost | Allowed API origins (comma separated) |
|
|
|
|
The host path for stacks is set via `STACKS_HOST_DIR` in `.env`.
|
|
|
|
## Local development
|
|
|
|
Backend:
|
|
|
|
```bash
|
|
cd backend
|
|
python -m venv .venv && source .venv/bin/activate
|
|
pip install -r requirements.txt
|
|
SECRET_KEY=dev STACKS_DIR=../data/stacks DATA_DIR=../data uvicorn main:app --reload --port 5008
|
|
```
|
|
|
|
Frontend (proxies to the backend on :5008):
|
|
|
|
```bash
|
|
cd frontend
|
|
npm install
|
|
npm run dev # http://localhost:5173
|
|
```
|
|
|
|
## API surface (Phase 1)
|
|
|
|
```
|
|
POST /api/auth/setup | login | refresh GET /api/auth/me | needs-setup
|
|
GET /api/stacks POST /api/stacks
|
|
GET /api/stacks/{id} PUT /api/stacks/{id} DELETE /api/stacks/{id}
|
|
POST /api/stacks/{id}/{start|stop|restart|pull|update|down|clone}
|
|
GET /api/stacks/{id}/logs GET /api/stacks/{id}/export
|
|
POST /api/stacks/convert (docker run → compose)
|
|
GET /api/system/info | gpus | devices GET /api/audit
|
|
WS /ws/logs/{stack_id}[/{service}] WS /ws/events
|
|
```
|
|
|
|
### Phase 2 endpoints
|
|
|
|
```
|
|
GET /api/volumes | /orphaned DELETE /api/volumes/{name}
|
|
POST /api/volumes/prune POST /api/volumes/generate-yaml
|
|
GET /api/host/paths?path=&show_hidden= (sandboxed browser)
|
|
POST /api/editor/services | add-volume | set-gpu | add-device | remove-device | set-privileged
|
|
```
|
|
|
|
### Phase 3 endpoints
|
|
|
|
```
|
|
GET /api/images | /updates POST /api/images/check
|
|
POST /api/ports/conflicts POST /api/editor/set-resources
|
|
GET /api/templates | /{id} POST /api/templates/{id}/instantiate
|
|
POST /api/templates DELETE /api/templates/custom/{slug}
|
|
```
|
|
|
|
## Security notes
|
|
|
|
- The Docker socket is only ever touched by the backend process; it is never
|
|
proxied to the browser.
|
|
- Login is rate-limited (10/min/IP).
|
|
- Compose files are backed up to `*.bak` before every overwrite.
|
|
- Generated YAML never includes the obsolete `version:` field and uses Compose v2
|
|
(`docker compose`) syntax.
|