Skip to content
All docs

Docs

Install

One command on a Linux host. Docker Swarm, Traefik, Postgres, and the panel.

Quick install

sh
curl -fsSL https://raw.githubusercontent.com/bablilayoub/nixploy/main/install.sh | sudo bash

With a domain (Let's Encrypt)

sh
1NIXPLOY_DOMAIN=panel.example.com NIXPLOY_LETSENCRYPT_EMAIL=you@example.com \ 2 curl -fsSL https://raw.githubusercontent.com/bablilayoub/nixploy/main/install.sh | sudo bash

Verify before you run it

The one-liner pipes a script into root's shell. Every release also ships a SHA256SUMS file, so the safer form is download, verify, then run.

sh
1VERSION=v0.7.3 2BASE="https://github.com/bablilayoub/nixploy/releases/download/$VERSION" 3curl -fsSLO "$BASE/install.sh" 4curl -fsSLO "$BASE/SHA256SUMS" 5sha256sum --ignore-missing -c SHA256SUMS # install.sh: OK 6sudo bash install.sh

The checksums cover the release assets, not the copies on the main branch — a release asset pins NIXPLOY_VERSION to its tag, the branch copy does not. The panel image itself is signed with cosign (keyless, GitHub OIDC); docs/install.md carries the exact cosign verify command.

Requirements

  • x86_64 or arm64 Linux with a root Docker daemon — rootless Docker is not supported
  • Ports 80 and 443 free for Traefik
  • 2 GB RAM minimum, 4 GB+ if you build images on the host
  • 5 GB free disk on both the config directory and the Docker root

What the installer does

  1. Installs Docker if needed and initializes Swarm
  2. Runs a preflight: ports 80/443, disk, memory, rootless Docker, and your domain's DNS record against this host's public IP
  3. Creates the two platform overlays — nixploy-network (Traefik-facing, joined only by services that have a domain) and nixploy-internal (panel ↔ Postgres, never any tenant container)
  4. Writes secrets under /etc/nixploy (mode 600) and generates a one-time setup token
  5. Pulls (or builds) the Nixploy image from GHCR
  6. Starts Postgres + Traefik + the panel, then waits for GET /api/ready through Traefik on loopback
  7. Prints the setup URL with the token, and the firewall one-liner for this host

A failing preflight stops the install before anything is downloaded. NIXPLOY_SKIP_PORT_CHECK=1 and NIXPLOY_SKIP_DNS_CHECK=1 override the two that can fail on a valid setup (a proxy in front, or a CDN-backed DNS record).

Useful overrides

  • NIXPLOY_VERSION / NIXPLOY_IMAGE — pin or override the app image
  • NIXPLOY_PORT — opt-in extra host port for plain-HTTP access (unset = Traefik only)
  • NIXPLOY_CONFIG_DIR — config root (default /etc/nixploy)
  • NIXPLOY_MEMORY_LIMIT — memory ceiling of the panel service (default 2g)
  • TZ — timezone every cron runs in (default UTC)
  • LOG_LEVEL / LOG_FORMAT=json — panel logging
  • TRUSTED_PROXIES — set to 1 by the installer so the real client IP is read from X-Forwarded-For behind Traefik. Without it every client IP reads as unknown and the IP-based rate limits collapse into one shared bucket
  • NIXPLOY_GITHUB_TOKEN — private-repo install/update
  • NIXPLOY_SKIP_DOCKER_INSTALL=1 — use an existing Docker daemon

Every runtime knob you set in the installer's environment is added to the nixploy service, so it survives later updates. The full table is in docs/install.md → Runtime environment.

Deadlines and retention

  • NIXPLOY_COMMAND_TIMEOUT_MS — every local shell/Docker command (default 30 minutes); NIXPLOY_REMOTE_COMMAND_TIMEOUT_MS is the SSH equivalent
  • NIXPLOY_DEPLOY_TIMEOUT_MS — per-deployment deadline (default 60 minutes); the job is cancelled and the row fails
  • NIXPLOY_CONVERGENCE_TIMEOUT_MS — how long a rollout may take to produce one running task (default 180 seconds)
  • NIXPLOY_HOOK_TIMEOUT_MS — pre/post-deploy hooks and image jobs (default 10 minutes)
  • NIXPLOY_METRICS_RETENTION_HOURS — metrics history window (default 48, maximum 720; 12 on the lite profile)
  • NIXPLOY_RUNTIME_LOGS, NIXPLOY_RUNTIME_LOG_RETENTION_DAYS, NIXPLOY_RUNTIME_LOG_MAX_MB_PER_SERVICE — the runtime log harvester and what it keeps per service (on, 7 days, 256 MB; off / 2 days / 64 MB on the lite profile)
  • NIXPLOY_REMEDIATION — 0 stops the rules that propose a rollback after a failed rollout or a restart loop
  • NIXPLOY_AUDIT_RETENTION_DAYS — audit log retention (default 365, 0 keeps rows forever)
  • NIXPLOY_DEPLOY_CONCURRENCY — deploy jobs built in parallel per target server (default 1)

Footprint, and the lite profile

The control plane is three containers: the panel, Postgres and Traefik. On the production box they measure 424, 51, 28 MiB, read on 20 Sep 2026 — reproduce it with docker stats --no-stream on any install. CI measures the panel a second way: the end-to-end job runs the full golden path (setup, two deploys including a build from source, a UI walk) against the release image and reads the container's resident memory afterwards, which has landed between 419 and 434 MiB. A run over the 768 MiB budget fails the build, so a change that doubles the panel's memory is caught before it reaches anyone's 4 GB box.

install.sh --lite (or NIXPLOY_LITE=1, which update.sh forwards) is the profile for a 1–2 GB box. It does not turn features off behind your back: it changes the defaults of five knobs, and each of them still wins when you set it, so a lite install that wants runtime logs back sets one variable and keeps the rest lean.

  • Runtime log harvesting off (NIXPLOY_RUNTIME_LOGS=1 brings it back); its history caps drop to 2 days / 64 MB per service
  • Metrics sampled every 2 minutes instead of 30 seconds, kept 12 hours instead of 48
  • Uptime probes every 2 minutes instead of 30 seconds
  • Four SSH channels per managed server instead of eight
  • NIXPLOY_MEMORY_LIMIT and NIXPLOY_WORKER_MEMORY default to 1g instead of 2g

Nothing about deploys, routing, TLS, backups, previews or the queue changes on the lite profile — it trades history and sampling resolution, never capability.

Split worker (optional)

By default Nixploy is one process: UI, API, websockets, the deploy queue and every cron share a memory limit. --split-worker moves the background half into its own Swarm service, nixploy-worker.

sh
1curl -fsSL https://raw.githubusercontent.com/bablilayoub/nixploy/main/install.sh \ 2 | sudo bash -s -- --split-worker 3 4# collapse back to one process 5sudo bash install.sh --no-split-worker
  • nixploy runs with NIXPLOY_ROLE=panel — UI, tRPC, REST, MCP, websockets; it still enqueues deploys and writes per-domain Traefik YAML
  • nixploy-worker runs with NIXPLOY_ROLE=worker — the deploy claim loop, boot recovery, every cron, and /api/health, /api/ready, /api/version
  • Updating the panel stops interrupting builds: a panel restart closes sockets in milliseconds while the worker keeps building
  • Separate memory ceilings (NIXPLOY_MEMORY_LIMIT and NIXPLOY_WORKER_MEMORY), so a 2 GB build cannot OOM the UI
  • The two halves coordinate over Postgres LISTEN/NOTIFY — no broker, no extra port, no schema change

The worker owns the migrations, so update.sh rolls it first. Deploy and cron output moves to docker service logs -f nixploy-worker. Re-running install.sh keeps the split without repeating the flag.

Health endpoints

  • GET /api/health — liveness, no dependencies
  • GET /api/ready — per-check readiness (database, Docker socket, migration state, deploy queue, Traefik, platform alerts); 503 with the failing checks listed
  • GET /api/version — version, commit, Node and Next versions for nixploy doctor and support

Air-gapped install

sh
1# on a machine with network access 2docker save ghcr.io/bablilayoub/nixploy:v0.2.0 postgres:17-alpine traefik:v3.7.13 \ 3 | gzip > nixploy-images.tar.gz 4 5# on the target host 6docker load < nixploy-images.tar.gz 7NIXPLOY_SKIP_DOCKER_INSTALL=1 NIXPLOY_PUBLIC_IP=10.0.0.5 \ 8NIXPLOY_IMAGE=ghcr.io/bablilayoub/nixploy:v0.2.0 NIXPLOY_SKIP_DNS_CHECK=1 \ 9 sudo -E bash install.sh

Update

sh
curl -fsSL https://raw.githubusercontent.com/bablilayoub/nixploy/main/update.sh | sudo bash

Updates keep .env, Postgres data, and Traefik ACME certs. A pg_dump is taken right before the roll (the last 3 are kept), migrations run on boot, and a new image that fails its /api/ready health check is rolled back automatically. On a split install both services are rolled, worker first.

  • Settings → Platform → Updates shows the GitHub release notes for the image the instance tracks, not just a digest
  • Update to a specific release: only the tag changes, never the registry or repository the instance already trusts
  • pinnedVersion is a ceiling for automatic updates — the checker logs “held back” instead of rolling past it
  • Downgrades are refused unless you allow them explicitly: migrations are forward-only, so restore the pre-update dump first

Uninstall

sh
curl -fsSL https://raw.githubusercontent.com/bablilayoub/nixploy/main/uninstall.sh | sudo bash

It prints what it will do and asks first. By default it removes the platform services (including nixploy-worker on a split install) and the two overlays, keeping the Postgres volume and /etc/nixploy — so re-running install.sh restores the instance. --purge deletes them after a typed confirmation; --tenants also removes your deployed services, which are otherwise left running.

Also see the repository guides under docs/ · REST API reference

Rent the box. Keep the rest.

One command, three services, and the first account is yours.