Self-hosted PaaS for Docker on your VPS
Open-source Coolify alternative β and a practical Dokku, CapRover, Dokploy,
Heroku, Vercel & Netlify alternative when you want git-to-deploy without the cloud bill.
Docs β’ Quick Start β’ Commands β’ Features β’ How it Works β’ Contributing
Docklift is a self-hosted PaaS (platform-as-a-service) that turns a plain Linux VPS into your own Docker deployment platform. Point it at a GitHub repo or upload a ZIP, and it builds a Docker image, runs the containers, wires custom domains with automatic Let's Encrypt HTTPS, and redeploys on every push β all from a web UI, with no vendor lock-in.
If you are searching for a Coolify alternative, a lighter Dokku / CapRover style workflow with a real dashboard, or a self-hosted Heroku / Vercel alternative that stays on hardware you control, Docklift is built for that job: deploy Docker apps on a VPS without Kubernetes, Swarm, or a managed cloud account.
Your server. Your rules. Your apps.
More screenshots
Project detail β All-services workspace (deploy / build / shared env) and per-service workspace (env, domains, storage, runtime logs); single-service projects stay flat

Live logs β every platform container, streamed over SSE

System monitoring β CPU, RAM, GPU, disk, network, processes

- Documentation site β quick start, full guides, commands
- Quick Start
- Features
- How it Works
- Deploy Your First App
- Builds: Dockerfile or Railpack
- Managed Databases
- Persistent Storage
- Domains & HTTPS
- Configuration
- Installation
- Development Setup
- Commands Reference
- Troubleshooting
- Why Docklift (vs Coolify, Dokku, CapRoverβ¦)
- FAQ
- Contributing
- License
Full walkthrough: docklift.dev/guide/quick-start Β· Commands: docklift.dev/guide/commands
One-command install on Ubuntu/Debian (latest GitHub release):
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install.sh | sudo bashPin a specific release (see Releases) β pass the version to bash, not in the curl URL:
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install.sh | sudo bash -s -- v=2.0.2curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install.sh | sudo DOCKLIFT_VERSION=2.0.2 bashThe installer prints your public URL, for example:
Dashboard: http://YOUR_SERVER_IP:8080
Setup code: <bootstrap-secret>
Open that URL, paste the setup code, and create the first admin account. You can keep using the IP:port panel, or later add an HTTPS panel domain under Settings β Domain.
Raw HTTP on a public IP is intentional for first-run convenience β it is not private or encrypted. Put DockLift behind a firewall, use HTTPS, or set
DASHBOARD_BIND=127.0.0.1if you want localhost-only access.
A fresh install prints a one-time bootstrap secret (setup code) to the install output and backend logs. The Setup page requires it before the first admin account can be created, so finding the IP alone is not enough to claim the panel. It is never exposed through any public API.
docker logs docklift-backend | grep -A8 "Fresh install"sudo cat /opt/docklift/data/.bootstrap-secretPaste it into the Setup page, register (or restore a backup), and the secret is consumed and deleted.
For HTTPS on a hostname instead of an IP, add a panel domain under Settings β Domain. Unknown hostnames on ports 80/443 are rejected β they never fall through to the dashboard.
| Feature | Description |
|---|---|
| π¦ One-Click Deploy | GitHub or ZIP β build β run on your VPS (Dockerfile or Railpack) |
| π§± Automatic Builds | Prefer your Dockerfile; otherwise Railpack detects Node, Python, Go, and more |
| π GitHub Integration | Private repos via a GitHub App you own β push-to-deploy webhooks |
| π Auto-Deploy | Webhook-triggered redeploys on push, with a 10s debounce |
| π Custom Domains + HTTPS | Nginx reverse proxy and automatic Let's Encrypt (Cloudflare Full-strict ready) |
| πΎ Persistent Storage | Named volumes per service so SQLite and uploads survive rebuilds |
| π§© Multi-Service Projects | Multi-Dockerfile apps with all-services vs per-service env, domains, and logs |
| π Live Build Logs | Real-time streaming output, with cancel mid-build |
| π Env Variables | Shared or per-service secrets; build-time and runtime scopes |
| π Host Monitoring | CPU, RAM, GPU, disk, network, and top processes β not just container stats |
| π» Web Terminal | Root shell in the browser (xterm.js), behind double auth |
| ποΈ Backup & Restore | Database, sources, vhosts, and certificates in one archive |
| π§ Sidebar Workspace | Collapsible rail plus Ctrl+K command palette |
Docklift is a small Docker Compose stack: a web UI, a backend that talks to the Docker socket, and nginx to route public traffic to your apps.
flowchart LR
Browser -->|:8080| DashNginx[docklift-nginx]
DashNginx --> UI[Frontend]
DashNginx --> API[Backend]
Internet -->|:80 / :443| AppProxy[nginx-proxy]
AppProxy -->|panel domain| DashNginx
AppProxy -->|project domain| AppCtr[Your app containers]
API -->|docker.sock| Docker[Docker Engine]
API -->|write vhosts| AppProxy
Certbot[certbot] -->|Let's Encrypt| AppProxy
GitHub -->|webhook / push| API
| Container | Role |
|---|---|
| docklift-frontend | React (Vite) dashboard β projects, logs, settings, terminal |
| docklift-backend | Express + Prisma/SQLite β deploy, auth, GitHub App, nginx/SSL |
| docklift-nginx | Dashboard gateway on :8080 |
| docklift-nginx-proxy | Public :80 / :443 for project and panel domains |
| docklift-certbot | Issues and renews Let's Encrypt certificates (HTTP-01) |
Two nginx containers, two different jobs:
docklift-nginxserves the panel,docklift-nginx-proxyserves your apps. The Logs page labels them Dashboard Gateway and Public Proxy for that reason.
- You create a project from a GitHub repo or a ZIP. Source lands in
deployments/<project-id>/. - Docklift uses your
Dockerfilewhen there is one, otherwise Railpack detects the stack and builds an image. - By default the app runs on a private project network (no public host port). Add a custom domain (preferred) so nginx-proxy serves it on
:80/:443, or opt in to Publish host ports (5500β5600pool) for rawIP:port. - When you set a domain, the backend writes an nginx vhost, then asks certbot for a certificate.
- With GitHub connected, a push webhook rebuilds and redeploys automatically.
Docklift writes its own runtime Compose file under deployments/.docklift/<project-id>/. Your
repository is never modified β a docker-compose.yml you committed yourself stays untouched.
- Admin UI:
http://SERVER_IP:8080by default. Prefer an HTTPS panel domain in Settings β Domain when you can. Optional:DASHBOARD_BIND=127.0.0.1+ SSH tunnel. - Your apps: public hostnames on
:80/:443via nginx-proxy (project networks; host ports opt-in). - Secrets:
JWT_SECRETand internal keys auto-generate and persist underdata/.secrets. First account requires the bootstrap secret.
- Open Docklift β New Project
- Choose a source: GitHub URL, private repo, or ZIP upload
- Add environment variables (optional) and pick a build mode (default: Auto)
- Deploy β watch live build logs (cancel any time)
- Add a domain (preferred) or enable Publish host ports + redeploy β Overview shows Private by default until then. Prefer domains: raw
IP:portexposes your origin server.
Every project has a build mode, configurable in project settings.
| Mode | Behaviour |
|---|---|
| Auto (default) | Uses a repository Dockerfile if present, otherwise falls back to Railpack |
| Dockerfile | Always your Dockerfile β fails loudly instead of silently falling back |
| Railpack | Always Railpack, even if a Dockerfile exists |
Railpack (the builder behind Railway) detects Node, Python, Go, PHP, Ruby, Java, Rust and more
from manifests like package.json, requirements.txt or pyproject.toml, then builds an image with
BuildKit β no Dockerfile required.
Two settings help with non-trivial repos:
- Base directory β build from a subdirectory, for monorepos.
- Dockerfile path β point at a Dockerfile that isn't at the root.
Build-time variables are passed only when the Dockerfile actually declares them with ARG, so
runtime secrets don't get baked into image layers.
Databases β New Database creates Postgres, MySQL, MariaDB, Redis, or MongoDB from official images (no Git/ZIP). Version tags load from Docker Hub automatically (majors / Alpine where available). Host ports stay off by default. Database detail tabs are Overview, Deployments, and Logs (credentials on Overview β Connection; no Environment / Build / Source / Domains).
Ports lists the opt-in host pool only. A Running app or database with publish off will not show as Allocated β use domain/linking, or enable Publish host ports and redeploy.
- Create & deploy the database.
- Open it β Link to a project (or one service), or from an app β Attach database.
- DockLift joins the DB container to the appβs Docker network and injects
DATABASE_URL/REDIS_URL/MONGODB_URIas a runtime secret. - Redeploy the app so containers pick up the new env.
Prefer linking over publishing host ports. Credentials are set at create time β recreate the database to rotate passwords (env edits are blocked so Connection URLs cannot lie about the server).
Redeploying replaces containers, so anything written inside a container's filesystem is lost unless it's on a volume. If your app keeps a SQLite database, uploaded files, or a cache inside the container, add a mount in the project's Storage tab.
- Volumes are external and labelled
com.docklift.project=<id>, sodocker compose downand redeploys never delete them. - They are removed only when you delete the project.
- External databases reached over
DATABASE_URL(Postgres, MySQL, managed services) need nothing here. For DockLift-managed databases, use Databases + link/attach instead of hand-pasting URLs.
# Inspect what Docklift manages
docker volume ls --filter label=com.docklift.project- Point an A record at your server's IP.
- Add the hostname in Docklift β project Domain for an app service, or Settings β Domain
for the panel (
panel.example.com). Same UI: DNS guide, inline add, Check DNS, Retry HTTPS, and Let's Encrypt activity. - Docklift writes an HTTP vhost, certbot solves the ACME HTTP-01 challenge, and the vhost is rewritten with HTTPS plus an HTTPβHTTPS redirect. Renewals run automatically every 12 hours.
www is not added for you. A certificate order fails entirely if one of its hostnames has no
DNS record, so silently adding www. would break the apex domain too. Create the www DNS record
first, then add www.example.com as an additional domain on the service.
Behind Cloudflare: use SSL mode Full (strict) once a certificate is issued, and make sure
Cloudflare can reach /.well-known/acme-challenge/ over plain HTTP during issuance.
If issuance fails, the UI shows the specific certbot error plus a copyable command for the full log:
docker logs docklift-certbot --tail 200All of these are optional. Create /opt/docklift/.env (Compose picks it up automatically) and
re-run docker compose up -d from that directory to apply changes:
| Variable | Default | Purpose |
|---|---|---|
DASHBOARD_BIND |
0.0.0.0 |
Panel listen address. Default allows http://SERVER_IP:8080. Set 127.0.0.1 for localhost-only, or use an HTTPS panel domain. |
PORT_RANGE_START / PORT_RANGE_END |
5500 / 5600 |
Host port pool when Publish host ports is enabled |
CERTBOT_EMAIL |
β | Let's Encrypt account email (expiry notices) |
CERTBOT_STAGING |
false |
Use the staging CA while testing, to avoid rate limits |
CORS_ORIGIN |
β | Extra allowed browser origins, if the panel isn't same-origin |
DOCKLIFT_FRONTEND_URL |
http://localhost:8080 |
Public dashboard URL, used for GitHub App callbacks |
JWT_SECRET / INTERNAL_API_SECRET |
auto-generated | Override only if you manage secrets yourself |
Install (latest release):
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install.sh | sudo bashInstall a specific release (bash -s -- v=2.0.2 or DOCKLIFT_VERSION=2.0.2):
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install.sh | sudo bash -s -- v=2.0.2curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install.sh | sudo DOCKLIFT_VERSION=2.0.2 bashPinning/downgrading with
install.shrebuilds that tag and does not runupgrade.shβs DB snapshot. Preferupgrade.shto move forward; backup before installing an older tag.
Upgrade to latest release (preserves data; always targets GitHub latest, not a pin):
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/upgrade.sh | sudo bashUninstall:
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/uninstall.sh | sudo bash -s -- -yUninstall removes every DockLift container, image, volume and network, the build cache, and
/opt/docklift (database, deployments, backups, certificates). Other Docker workloads on the same
host are left alone, as are Docker Engine and git.
curl -fsSL https://raw.githubusercontent.com/SSujitX/docklift/master/install-dev.sh | sudo bash
β οΈ Installs unreleased code frommaster. Use the production install for stable deployments.
git clone https://github.com/SSujitX/docklift.gitcd dockliftdocker compose up -dPrerequisites: Docker and Bun.
git clone https://github.com/SSujitX/docklift.git
cd dockliftBackend β localhost:8000:
cd backend
cp .env.local.example .env.local # your machine only (gitignored)
bun install
bun run db:generate && bun run db:push
bun run devFrontend (new terminal) β localhost:3600:
cd frontend && bun install && bun run devEnv files: backend/.env is server/production config (committed).
backend/.env.local.example is the local template (committed) β copy to .env.local (gitignored)
for the Vite dev origin in CORS_ORIGIN and optional GitHub App credentials.
The .agent/skills/ directory documents each subsystem (deployments, networking, security, UI) and
is the fastest way to get oriented in the codebase.
| Command | Description |
|---|---|
docker logs docklift-backend -f |
Backend / API |
docker logs docklift-frontend -f |
Dashboard SPA |
docker logs docklift-nginx -f |
Dashboard gateway (:8080) |
docker logs docklift-nginx-proxy -f |
Public proxy (:80/:443) |
docker logs docklift-certbot -f |
Certificate issuance and renewals |
docker ps --filter name=dl_ |
List project containers |
Run from backend/:
| Command | Description |
|---|---|
bun run db:studio |
Open Prisma Studio GUI |
bun run db:migrate |
Apply checked-in Prisma migrations |
bun run db:ensure |
Production DB bootstrap (dedupe + migrate + repair) |
bun run db:generate |
Regenerate the Prisma client |
bun run db:push |
Local-only schema sync (not used on container boot) |
| Command | Description |
|---|---|
docker exec -it docklift-backend node dist/scripts/reset-password.js |
Reset admin password |
docker exec docklift-nginx-proxy nginx -t |
Validate generated vhosts |
for port in {5500..5600}; do sudo fuser -k ${port}/tcp 2>/dev/null; done |
Free the app port pool |
π Full command guide: commands.md
Docklift uses semantic-release for
versioning, changelogs and GitHub Releases. Do not hand-edit version fields or CHANGELOG.md.
# Assume current version is 1.3.21 (root + frontend + backend stay in sync)
# 1) Commit with conventional messages on master
git commit -m "fix(deploy): description" # β patch β 1.3.22
git commit -m "feat(api): description" # β patch β 1.3.22
git commit -m "chore: cleanup" # β patch β 1.3.22
git commit -m "feat: something *force minor*" # β minor β 1.4.0
git commit -m "feat: something *force major*" # β major β 2.0.0
git commit -m "chore: docs *skip release*" # β none β stays 1.3.21
# 2) Push β wait for CI + Install (Ubuntu) to go green
# 3) GitHub β Actions β "Release" β Run workflow
# semantic-release bumps package.json (root/frontend/backend), CHANGELOG.md, tag + GitHub Release
# Release then deploys Docs (changelog on docklift.dev)| Commit signal | Release | Demo (1.3.21 β) |
|---|---|---|
feat:, fix:, perf:, refactor:, docs:, test:, ci:, chore: β¦ |
Patch | 1.3.22 |
*force minor* in subject |
Minor | 1.4.0 |
*force major* / BREAKING CHANGE |
Major | 2.0.0 |
*skip release* in subject |
None | 1.3.21 (unchanged) |
SemVer reminder: patch = bug fix / small change, minor = new feature (forced here), major = breaking change.
π Full guide: commands.md Β· Config: release.config.cjs Β· Workflow: .github/workflows/release.yml
| Symptom | Likely cause / fix |
|---|---|
| Can't get past Setup | Bootstrap secret required β see First login |
| Build fails with "no Dockerfile" | Set build mode to Railpack, or fix Base directory for a monorepo |
502 Bad Gateway on a domain |
Container not running, or the app isn't listening on the configured internal port |
Domain returns nothing / NXDOMAIN |
DNS record missing or not propagated; flush your local resolver cache |
| Certificate order fails | One hostname in the request has no DNS record β check docker logs docklift-certbot |
| Data disappears after redeploy | Add a mount in the project's Storage tab |
| "A deployment is already running" | Cancel the in-flight build first; deployments are serialized per project |
If a deployed app is blocked by Cloudflare but works from your SSH shell, Docker's network fingerprint is being detected. Run a lightweight proxy on the host network:
docker run -d --name local-host-proxy --network host --restart unless-stopped \
-e PROXY_USER=user -e PROXY_PASSWORD=pass serjs/go-socks5-proxyThen route your app's requests (e.g. curl_cffi, Python requests) through
socks5://user:pass@172.28.0.1:1080. Traffic then leaves through the physical host network, exactly
like your SSH session.
People comparing self-hosted PaaS options usually land on Coolify, Dokku, CapRover, or Dokploy. Those are solid projects. Docklift aims at a narrower brief: a readable Docker deployment panel for a single VPS (or a small fleet of apps on one host), without Swarm/Kubernetes ceremony.
| You want⦠| Typical pick | Why Docklift may fit instead |
|---|---|---|
| Polished UI + huge one-click catalog | Coolify / Dokploy | Smaller surface: GitHub/ZIP β Docker β domain β HTTPS |
CLI git push Heroku clone |
Dokku | Full web UI, live logs, host metrics, in-browser terminal |
| Swarm / one-click app store | CapRover | Plain Docker Compose under the hood β no Swarm required |
| Managed DX (Vercel / Netlify / Heroku) | Cloud PaaS | Same βpush and get a URLβ feel on your VPS and Docker host |
What Docklift optimizes for
- Self-hosted Docker deploys with a real dashboard (not CLI-only)
- Dockerfile-first builds, with Railpack when you do not have one
- Automatic HTTPS and custom domains via nginx + Let's Encrypt
- Multi-service projects (shared vs service env, domains, storage, runtime logs)
- Ops visibility built in: host monitoring and a gated web terminal
Honest trade-offs: Docklift is not trying to be the largest template marketplace or a multi-node orchestrator. If you need 200+ one-click apps or Docker Swarm clustering on day one, Coolify, Dokploy, or CapRover may match better. If you want a focused open-source Coolify alternative (or a Dokku-like deploy model with a UI) to run containers on a VPS you control, start here.
Yes β in the sense that both are open-source, self-hosted platforms for deploying apps with Docker, domains, and HTTPS on your own server. Coolify is broader (templates, multi-server stories). Docklift stays lighter and pairs deploys with host monitoring and a web terminal.
Dokku is CLI-first and buildpack-oriented; CapRover leans on Docker Swarm and one-click apps. Docklift gives you a web UI, Dockerfile/Railpack builds, nginx HTTPS, and Compose-backed projects without requiring Swarm.
For many apps, yes: connect GitHub (or upload a ZIP), build, attach a domain, get TLS, and auto-redeploy on push. You keep the VPS, Docker socket, and data β no Heroku/Vercel account required.
No. Docklift talks to the Docker engine on the host and runs a small Compose stack for the panel plus your app containers.
A typical Ubuntu/Debian VPS with Docker installed is enough to install and deploy your first app. Size the box for your workloads; the panel itself is a few containers, not a full cluster control plane.
Contributions are welcome:
- Fork the repo
- Create a feature branch (
git checkout -b feature/amazing) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push the branch (
git push origin feature/amazing) - Open a Pull Request
Before opening a PR, please run:
cd backend && bun run build && bun run test
cd frontend && bun run buildfeat: New feature
fix: Bug fix
docs: Documentation
style: Formatting (no code change)
refactor: Code restructure
test: Add tests
chore: Maintenance
By default every conventional type ships a patch. Force a larger bump (or skip) in the subject:
*force minor* β minor
*force major* β major (or BREAKING CHANGE)
*skip release* β no release
See the Releases section above for a worked 1.3.21 example.
MIT License β see LICENSE
Made with β€οΈ for developers who take control
