Documentation
Everything you need to self-host.
PullPilot manages updates for the Docker images and services in your homelab — status, logs and deployment modes — from a single UI. Here's how to get it running and tuned.
Quick install
No .env is required if you use the default stacks path /srv/docker-stacks. The official compose sets ALLOW_NO_AUTH=true so the quick start works on a trusted LAN.
$ sudo mkdir -p /srv/docker-stacks
$ mkdir -p ~/pullpilot && cd ~/pullpilot
$ curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/KN990x/PullPilot/main/docker-compose.yml
$ curl -fsSL -o .env.example https://raw.githubusercontent.com/KN990x/PullPilot/main/.env.example
$ docker compose up -dThen open http://your-server-ip:8000 (or the host port from PULLPILOT_PORT).
After startup
Each project is a subfolder under your stacks root with a docker-compose.yml (or docker-compose.yaml) inside. Keep PullPilot's own compose folder outside that tree when you can.
/srv/docker-stacks/ # default DOCKER_ROOT_PATH
├── plex/
│ └── docker-compose.yml
├── uptime-kuma/
│ └── docker-compose.yml
└── ...Folders named pullpilot, pullpilot-ui, docker-updater and data are ignored under the stacks root.
Using a different stacks location
Create the folder on the host, then add an .env next to docker-compose.yml pointing at it. The path must be identical on the host and inside the container, because it is bind-mounted one-to-one. After any .env change, run docker compose up -d (or docker compose restart).
DOCKER_ROOT_PATH=/absolute/path/to/stacksUsage guide
Dashboard
A card per project — status, per-project update, and Full stop and Exclude toggles.
Update All
Scans non-excluded projects, runs git pull where applicable, recreates containers; summary in History.
Schedule
Default global update daily at 04:00 (container time), or define your own per project.
Environment variables
A single list for your Compose .env. Copy .env.example and override only what you need.
| Variable | Default | Description |
|---|---|---|
| DOCKER_ROOT_PATH | /srv/docker-stacks | Same absolute stacks path on host and container (bind mount). |
| PROJECTS_ROOT | (from DOCKER_ROOT_PATH) | Advanced: use only when the path inside the container must differ from the bind mount. |
| PULLPILOT_PORT | 8000 | Published host port for the UI. |
| TZ | UTC | Container timezone. |
| DATA_DIR | /app/data | SQLite and runtime data (the official compose uses the pullpilot_data volume). |
| ALLOW_NO_AUTH | false* | *The official compose defaults to true. If false, AUTH_USER and AUTH_PASS are required or startup fails. |
| AUTH_USER / AUTH_PASS | — | Required when ALLOW_NO_AUTH=false. If both are set, session login is enforced. |
| SESSION_SECRET | (generated) | Fixed value ⇒ sessions survive restarts. Required if UVICORN_WORKERS > 1. Generate one with openssl rand -hex 32. |
| SESSION_HTTPS_ONLY | false | Set true if the app is only served over HTTPS. |
| SESSION_SAME_SITE | lax | Cookie SameSite: lax, strict or none. Use none only over HTTPS — browsers require Secure. |
| CORS_ORIGINS | (empty) | Comma-separated origins. Empty is usually fine when the SPA is served by the same app. |
| HEALTHCHECK_TIMEOUT | 60 | Post-deploy health wait, in seconds. |
| COMMAND_TIMEOUT | 300 | External command timeout, in seconds. |
| LOG_LOCALE | es | Language for scheduled update logs and history (es or en). UI-triggered updates follow Accept-Language instead. |
| LOGIN_RATE_LIMIT_ENABLED | true | In-memory login rate limit, per IP. |
| LOGIN_RATE_LIMIT_MAX | 15 | Max login attempts per window. |
| LOGIN_RATE_LIMIT_WINDOW_SEC | 300 | Rate limit window length, in seconds. |
| TRUST_X_FORWARDED_FOR | false | Use X-Forwarded-For for the login rate limit. Enable only behind a proxy you trust. |
Security notes
Treat PullPilot like root access — the Docker socket is mounted. Never expose port 8000 to the public internet without TLS, ALLOW_NO_AUTH=false, strong credentials, and ideally an extra auth layer (Authelia, Authentik…).
Updates and scheduled jobs only run under the resolved stacks root. Database paths outside that tree are rejected.
Behind a TLS-terminating proxy, set SESSION_HTTPS_ONLY=true, and set a fixed SESSION_SECRET so sessions survive restarts.
Run one Uvicorn worker per instance — the scheduler and the login rate limit are in-memory. If you ever set UVICORN_WORKERS above 1, you must set SESSION_SECRET so every worker shares the same signing key.
The published image is ghcr.io/kn990x/pullpilot. If you still pin ghcr.io/kernel-nomad/pullpilot, update your compose file — that path is no longer maintained.