Installation
Requirements
- Linux, macOS, or Windows host with Docker or Podman running
docker compose(v2) available inPATHif you plan to use Paste & Deploy or Git Deployment's compose mode — DockPod checks for it on startup and warns if it's missinggitavailable inPATHfor Git Deployment (clone/fetch on every deploy)- No database server to install — DockPod uses an embedded pure-Go SQLite driver (no CGO)
Quick Install (Linux)
curl -fsSL https://dockpod.io/install.sh | shThis installs the dockpod binary to /usr/local/bin, sets up a systemd service (enabled and started immediately), and — on Ubuntu/Debian (apt) or RHEL/CentOS/Rocky/Alma/Fedora (dnf/yum) — checks for and installs any missing dependencies first:
- git, if missing
- Docker, via the official convenience script, but only if neither Docker nor Podman is already present — a host you've deliberately set up with Podman is left alone
- docker compose v2 plugin, if the
dockerCLI is present but the plugin isn't - Nixpacks and Railpack, used by Git Deployment's zero-config build types — installed via each tool's own official install script. A failure here (e.g. no internet access) is a warning, not a fatal error; that build type just shows as unavailable in the deploy-mode picker until installed manually
On an unrecognized distro (anything without apt/dnf/yum), the dependency checks are skipped with a warning rather than failing the install — install Docker/Podman, docker compose, git, and (optionally) Nixpacks/Railpack yourself first.
Re-running the script plain (no flags) is safe — each dependency check is a no-op if already satisfied, and it always re-downloads and reinstalls the dockpod binary itself. Note that it does not restart the running service on its own: systemctl enable --now is a no-op start on a unit that's already active, so the new binary sits on disk until the next restart (sudo systemctl restart dockpod, or reboot) — unless you use --update below, which handles that for you.
Updating
curl -fsSL https://dockpod.io/install.sh | sh -s -- --update--update skips the OS-level setup (dependency installs, fail2ban, firewall — assumed already done on an existing install) and just re-downloads the binary and restarts the dockpod service, so the new version takes effect immediately. This is the fast path for picking up a new release; the plain command above is really a first-install flow.
Pin a specific version instead of the latest release by adding it after --update:
curl -fsSL https://dockpod.io/install.sh | sh -s -- --update v1.2.3(A version can be pinned the same way on a first install too — sh -s -- v1.2.3, without --update.)
Firewall & fail2ban
On the same recognized distros (apt/dnf/yum), the installer also hardens the host by default:
- fail2ban — installed and enabled with
[sshd],[sshd-ddos], and[recidive]jails (24h ban after 3 failed SSH attempts within 10 minutes, escalating for repeat offenders). - Firewall —
ufwon Debian/Ubuntu,firewalldon RHEL-family, enabled with a default-deny-incoming policy. Before enabling, it opens exactly five ports: SSH, 80, 443, 8080 (panel), and 8090 (MCP).
The SSH port is auto-detected from /etc/ssh/sshd_config rather than assumed to be 22 — if you've moved SSH to a custom port, the firewall rule follows it, so enabling default deny incoming doesn't lock you out.
If you override --addr/--mcp-addr away from their :8080/:8090 defaults, the firewall rules won't automatically follow — open the new ports manually (ufw allow <port>/tcp or firewall-cmd --permanent --add-port=<port>/tcp && firewall-cmd --reload).
On an unrecognized distro, both are skipped with a warning, same as the dependency checks above.
Running the Binary Directly
./dockpod --addr :8080 --data-dir /var/lib/dockpodFlags & Environment Variables
Every flag has a matching environment variable, useful for running under systemd or in a container.
| Flag | Env var | Default | Description |
|---|---|---|---|
--addr | DOCKPOD_ADDR | :8080 | Main web UI + REST API listen address |
--mcp-addr | DOCKPOD_MCP_ADDR | :8090 | MCP server listen address (separate port — see MCP Setup) |
--data-dir | DOCKPOD_DATA_DIR | ./data | Where the SQLite DB, git clones, and SSH keys are stored |
--socket | DOCKPOD_SOCKET | (auto-detect) | Explicit Docker/Podman socket path |
--jwt-secret | DOCKPOD_JWT_SECRET | (auto-generated) | JWT signing secret — generated once and persisted in the DB if not set |
Socket Auto-detection
If --socket isn't set, DockPod checks, in order:
/var/run/docker.sock/run/podman/podman.sock
Podman exposes a Docker-compatible API on its socket, so the same client code works against either engine unmodified.
Podman Compatibility
Tested end-to-end against a real Podman machine (Podman 6.0.0, rootless, API version 1.44 negotiated automatically):
- ✅ Container list / start / stop / restart / remove
- ✅ Image list / pull (streamed progress) / remove / prune
- ✅ Live memory updates (Optimize's Memory, both Git Deployment apps and Container Detail) — confirmed the change actually lands at the cgroup level inside the container (
/sys/fs/cgroup/memory.max), not just accepted by the API and silently ignored - ✅ Replica creation (Optimize's Scale) via
PublishAllPorts - ✅ Compose-based deploys (Paste & Deploy, Git Deployment compose mode) — see the
DOCKER_HOSTnote below
A real bug surfaced during this testing and has since been fixed: image pulls returned 200 OK with an empty body and silently pulled nothing. The cause was DockPod-side — a stream was closed before it had been fully read — not a Podman incompatibility; raw calls to Podman's API worked correctly throughout. Docker didn't expose the bug because dockerd keeps pulling in the background even after the client connection drops; Podman ties the pull to the connection staying open, which is what made it visible.
A second bug surfaced testing compose-based deploys specifically: DockPod shells out to the docker compose CLI for these (the compose plugin isn't exposed via the daemon API), and that subprocess had no DOCKER_HOST set, so it fell back to its own default socket detection instead of necessarily matching whichever socket DockPod's own client connected to. On a host with both Docker and Podman present, or with DOCKER_HOST already set to something else in the ambient environment, this could silently deploy to the wrong engine. Fixed by explicitly pinning the subprocess's DOCKER_HOST to the exact socket DockPod itself resolved — confirmed via a real Paste & Deploy compose stack landing in Podman specifically (absent from a separately-running real Docker daemon on the same machine) and reachable on its published port.
Not yet verified against Podman specifically (works against Docker; expected to work given the shared Docker-compatible API, but hasn't been exercised end-to-end): the Traefik reverse-proxy/custom-domains feature. If you hit an issue on Podman, please open an issue.
Running Podman on Apple Silicon macOS
podman machine uses the libkrun provider on Apple Silicon, which needs the external krunkit helper binary. If podman machine start fails with krunkit exited unexpectedly or, with --log-level debug, error: unexpected argument '--timesync' found, your krunkit build is stale (this affects the slp/krunkit Homebrew tap in particular). Install the current release from containers/krunkit instead.
Running as a systemd Service
[Unit]
Description=DockPod
After=docker.service
Requires=docker.service
[Service]
ExecStart=/usr/local/bin/dockpod --data-dir /var/lib/dockpod
Restart=on-failure
User=root
[Install]
WantedBy=multi-user.targetDocker socket access
DockPod needs access to the Docker socket, which is effectively root-equivalent access to the host. Only expose DockPod's web UI over HTTPS (via a reverse proxy) and keep login credentials strong — anyone who can log in can, in effect, run arbitrary code on the host via container creation.