Lightweight, open-source, self-hosted PaaS. Deploy web apps on a single VPS.
DeployX is a self-hosted alternative to Railway, Render, and Coolify. It lets you deploy and manage multiple web applications on a single VPS with a clean dashboard and API -- no vendor lock-in, no per-seat pricing, no usage-based surprises.
- Affordable hosting. Run multiple apps on a single $6/month VPS instead of paying per-app on managed platforms.
- Minimal overhead. ~200MB memory footprint vs 2GB+ for alternatives like Coolify.
- Security-first design. No shell string interpolation, hardened containers, encrypted secrets, Docker socket proxy isolation.
- Multi-language support. Deploy Node.js, Python, Go, Rust, Ruby, and more via Nixpacks auto-detection.
- Automatic SSL. Traefik v3 handles TLS certificates via Let's Encrypt with zero configuration.
- Simple deployment. Push a git repo, Docker image, or tarball. DeployX builds and routes it automatically.
![]() |
![]() |
| Sign-in screen | Account creation |
![]() |
![]() |
| Projects list (empty state) | New-project form |
![]() |
![]() |
| New-project form (filled) | Settings |
Dashboard built with SvelteKit (Svelte 5 runes), Tailwind, dark surface palette. All screenshots from local dev mode.
Deploy DeployX on a fresh VPS with a single command:
curl -fsSL https://raw.githubusercontent.com/iEmmanuel104/deployx/main/infra/installer/install.sh | sudo bashThis single command:
- Installs Docker, Node.js, pnpm, and Nixpacks
- Generates encryption keys and JWT secrets
- Creates a system user and required directories
- Runs database migrations
- Starts all services via Docker Compose
- Verifies health and prints the dashboard URL
Supported operating systems: Ubuntu 22.04, Ubuntu 24.04, Debian 12
For non-interactive installation (CI/CD or scripted setup):
PLATFORM_DOMAIN=deployx.example.com ACME_EMAIL=you@email.com \
curl -fsSL https://raw.githubusercontent.com/iEmmanuel104/deployx/main/infra/installer/install.sh | sudo bash| Resource | Minimum | Recommended |
|---|---|---|
| OS | Ubuntu 22.04 / 24.04, Debian 12 | Ubuntu 24.04 |
| CPU | 1 vCPU | 2+ vCPU |
| RAM | 1 GB | 2+ GB |
| Storage | 20 GB | 40+ GB (Docker images take space) |
| Network | Public IPv4 | Public IPv4 + domain with DNS access |
Not supported for production: Windows, macOS, CentOS, Fedora, Arch, Alpine.
Local development (any OS): Use Docker Desktop with docker-compose.dev.yml -- works on Windows, macOS, and Linux.
git clone https://github.com/iEmmanuel104/deployx.git
cd deployx
pnpm install
cp .env.example .env # Then fill in secrets (see Environment Variables below)
pnpm db:generate && pnpm db:migrate
pnpm dev # API on :3001, Dashboard on :3000Or with Docker:
docker network create proxy-network
docker compose -f docker-compose.dev.yml up --build
# Dashboard: http://localhost:3000
# API: http://localhost:3001Dev mode note: the API does not currently auto-load
.env(nodotenvimport inapps/api/src/index.ts). Forpnpm devyou'll need to eitherexportthe variables in your shell, run packages individually (pnpm -F @deployx/api devwith env prefixed), or wait on the upcoming env-loader fix tracked in the roadmap below.
After DeployX is running on your VPS, deploy your first project from the CLI:
# 1. Install the CLI (one-time)
npm install -g @deployx/cli
# 2. Authenticate against your DeployX instance
deployx login --server https://deployx.example.com
# Prompts for email + password (the admin you created during install)
# 3. Create a project from a git repo
deployx projects create \
--name "hello-world" \
--slug "hello-world" \
--git "https://github.com/heroku/python-getting-started.git" \
--branch "main" \
--build "nixpacks" \
--port 5000
# 4. Set environment variables
deployx env set DATABASE_URL "postgres://..."
deployx env set SECRET_KEY "$(openssl rand -hex 32)"
# 5. Trigger the first deploy
deployx deploy
# 6. Attach a custom domain (optional — the default subdomain
# <slug>.your-platform-domain is created automatically)
deployx domains add hello-world.example.com
# 7. Verify
curl -I https://hello-world.your-platform-domainYou can do all of the above from the web dashboard at https://${PLATFORM_DOMAIN} instead. The CLI is mostly useful for scripting and CI.
Copy .env.example to .env and configure:
| Variable | Required | Default | Description |
|---|---|---|---|
PLATFORM_DOMAIN |
Yes | -- | Your domain (e.g., deployx.example.com) |
ENCRYPTION_KEY |
Yes | -- | 64 hex chars for AES-256-GCM. Generate: openssl rand -hex 32 |
JWT_SECRET |
Yes | -- | 64 hex chars for JWT signing. Generate: openssl rand -hex 32 |
DB_PATH |
No | /data/platform.db |
SQLite database file path |
PORT |
No | 3001 |
API server port |
NODE_ENV |
No | production |
Environment (production or development) |
ACME_EMAIL |
No | -- | Email for Let's Encrypt SSL certificate registration |
DEPLOYX_VERSION |
No | latest |
Platform version tag for Docker images |
DeployX uses a four-plane layered architecture:
- Infrastructure Plane -- Docker Engine runs containers; Traefik v3 handles reverse proxying, TLS termination, and automatic SSL via Let's Encrypt.
- Control Plane -- A Fastify 5 API server manages projects, deployments, and domains. A SQLite-backed job queue handles async operations (builds, health checks, cleanup).
- Presentation Plane -- A SvelteKit (Svelte 5) dashboard provides the web UI. A Commander.js CLI offers terminal-based management.
- Data Plane -- SQLite in WAL mode (via Drizzle ORM) stores all platform state. The filesystem stores build artifacts and logs.
deployx/
├── apps/
│ ├── api/ # Fastify API server (port 3001)
│ ├── dashboard/ # SvelteKit web UI (port 3000)
│ └── cli/ # Commander.js CLI tool
├── packages/
│ ├── builder/ # Nixpacks build wrapper
│ ├── crypto/ # AES-256-GCM encryption utilities
│ ├── db/ # Drizzle ORM schema + SQLite client
│ ├── docker/ # Docker client with security defaults + Traefik labels
│ ├── types/ # Shared Zod schemas and TypeScript types
│ └── config/ # Platform config loader (deployx.yaml + env)
└── infra/
├── traefik/ # Traefik reverse proxy configuration
├── installer/ # VPS one-line setup script
└── systemd/ # systemd service unit
- Create a project. Point DeployX at a git repository, Docker image, or uploaded archive.
- Automatic build. DeployX clones the source and builds it with Nixpacks, which auto-detects the language and framework.
- Hardened container. A Docker container is created with security defaults: all capabilities dropped, no-new-privileges, seccomp and AppArmor profiles, PID limits.
- Automatic routing. Traefik picks up the new container via Docker labels and configures routing with automatic TLS certificates.
- Live. Your app is accessible at
your-project.your-domain.com.
Projects are managed through the web dashboard, the CLI, or the REST API. No per-project infrastructure setup is needed -- add repos and deploy.
DeployX is built with a security-first approach. These measures are enforced at the code level, not left to configuration:
- No shell string interpolation. All subprocess calls use
execFile()with parameterized argument arrays. This eliminates the command injection class of vulnerabilities entirely. - Container hardening. Every container runs with
CapDrop: ALL,SecurityOpt: no-new-privileges, seccomp and AppArmor profiles, and a PID limit of 256. - Build isolation. Build containers run with
NetworkMode: noneto prevent exfiltration during builds. - Encrypted environment variables. Secrets are stored with AES-256-GCM encryption using per-project keys derived via HKDF.
- Docker socket proxy. Application code never accesses the Docker socket directly. All Docker API calls go through a restricted proxy that allowlists specific endpoints.
- Authentication. JWT access tokens with 15-minute expiry, httpOnly SameSite=Strict refresh cookies, and rate limiting on auth endpoints (5 failures = 15-minute lockout).
| Layer | Technology |
|---|---|
| Runtime | Node.js 22 LTS |
| Language | TypeScript (strict mode) |
| API | Fastify 5 |
| Dashboard | SvelteKit (Svelte 5) |
| Database | SQLite + Drizzle ORM |
| Containers | Docker + Traefik v3 |
| Builds | Nixpacks |
| Monorepo | Turborepo + pnpm |
- DNS not pointing at this VPS yet. Verify with
dig +short ${PLATFORM_DOMAIN} @1.1.1.1— must return your VPS IP. - Port 80 is blocked. Let's Encrypt's HTTP-01 challenge requires inbound port 80. Check:
ufw statusand your provider's firewall rules. - Rate-limited. Let's Encrypt allows 5 duplicate certs per week per domain. Watch for "too many certificates" in Traefik logs and back off.
- API container isn't running.
docker compose psshould showdeployx-apiasUp. If not,docker compose logs apito see why. - API crashed mid-startup — usually missing env var or DB path. Check
/etc/deployx/.envexists and is readable by the deployx user.
DeployX uses SQLite in WAL mode. If you accidentally ran pnpm with PM2 cluster mode (or any setup with multiple writer processes), the WAL file can get corrupted. Fix:
# Stop everything
docker compose down
# Force checkpoint + remove WAL
sqlite3 /data/platform.db "PRAGMA wal_checkpoint(TRUNCATE);"
rm -f /data/platform.db-shm /data/platform.db-wal
docker compose up -dPM2 must run DeployX API in fork mode, never cluster mode.
- Nixpacks needs network access during build. The build container runs with
NetworkMode: nonefor security — DeployX pre-fetches packages, but custom Dockerfiles bypass this. Checkdocker compose logs api | grep nixpacks. - Disk full. Builds accumulate in
/builds/and DeployX does not yet garbage-collect them (see Roadmap). Manually prune:rm -rf /builds/old-*.
If the VPS is lost or platform.db is corrupt and you've been replicating via
Litestream, restore is a single command. From a fresh DeployX install (with
/etc/litestream.yml already filled in):
# Stop the platform so nothing writes during the restore
cd /opt/deployx && docker compose down
# Pull the most recent replicated state into a side file, then swap it in
litestream restore -o /data/platform.db.restored -if-replica-exists -config /etc/litestream.yml /data/platform.db
mv /data/platform.db /data/platform.db.old
mv /data/platform.db.restored /data/platform.db
# Quick sanity check
sqlite3 /data/platform.db "SELECT COUNT(*) FROM users;"
# Bring it back up — Litestream will resume replication
docker compose up -d
systemctl restart litestreamFor a brand new VPS that never had Litestream running locally yet, run the
installer first, fill in /etc/litestream.yml with the original bucket
credentials, then run the restore above instead of the empty migrate
seed. The DB schema comes along for free since Litestream replicates the
file as-is.
The slug must be globally unique across all users on a single DeployX instance. Pick a different slug or delete the conflicting project.
If you're running pnpm dev locally:
- The API isn't reachable from the dashboard (most often: API not running, wrong port, or the Vite proxy is misconfigured).
- The
/api/v1proxy inapps/dashboard/vite.config.tsmust NOT swallow/api/auth/*(those are SvelteKit-local routes for session cookies). Confirm the proxy key is/api/v1, not/api.
DeployX takes ports 80 and 443. Traefik v3 binds both for HTTP→HTTPS redirect and Let's Encrypt's HTTP-01 challenge. If your VPS already runs Caddy, nginx, Apache, or another DeployX instance on those ports, you have three options:
- Stop the existing proxy and migrate its sites into DeployX as projects. Cleanest end-state, requires care during cutover.
- Move DeployX to a second VPS. Recommended when the existing reverse proxy hosts production traffic you don't want to disrupt. DeployX is light enough that a $5–6/month VPS handles dozens of small apps.
- Front DeployX with the existing proxy. Configure your existing proxy to terminate TLS and reverse-proxy to DeployX on alternative ports. Loses DeployX's built-in cert management; only choose this when option 1 or 2 is impossible.
Case study: the SwiftEagle deployment (
swifteagledelivery.info) runs on its own Caddy on Contabo VPS #1. DeployX runs (will run) on Contabo VPS #2 withdeploy.synquanta.com. Separate VPSes, zero conflict.
DeployX is MVP-stage. The following items are tracked and being addressed:
| Status | Item | Notes |
|---|---|---|
| ✅ | Log streaming | GET /api/v1/projects/:id/logs?follow=1&tail=200 streams demuxed Docker container logs as Server-Sent Events (with 15s heartbeats). CLI: deployx logs <project> -f. Dashboard: live Logs tab on each project. |
| ✅ | /readyz DB validation |
/readyz runs PRAGMA quick_check(1) against the platform DB and returns 503 with the failure detail when the probe fails. |
| ✅ | Build garbage collection | POST /api/v1/system/gc?keep=N&dryRun=1 plus deployx builds gc CLI walk /builds/, group by project slug, keep the N most recent per slug, remove the rest. Reports scanned/kept/removed/bytes-freed. |
| ✅ | Litestream / S3 backup | Installer downloads Litestream and writes /etc/litestream.yml template targeting any S3-compatible bucket (R2, B2, AWS S3, MinIO). Restore procedure documented above under Troubleshooting. |
| ✅ | API .env auto-loading in dev |
apps/api/src/index.ts imports dotenv/config at the top — pnpm dev reads .env directly. Production (systemd-sourced /etc/deployx/.env) unaffected. |
| 🚧 | Webhook triggers | DeploymentTrigger.git_push exists in the schema but no webhook receiver is wired up. Use the CLI or dashboard "Deploy" button for now. |
| 🚧 | Custom Dockerfile builds | Schema supports buildType=dockerfile but the builder currently only handles Nixpacks. |
| ✅ | Installer host hardening | install.sh enforces UFW default-deny + 22/80/443 allow, SSH key-only auth via drop-in at /etc/ssh/sshd_config.d/99-deployx-hardening.conf (with a safety guard that refuses to disable password auth when /root/.ssh/authorized_keys is empty), fail2ban, and unattended-upgrades. |
| ✅ | Installer idempotency | install.sh is safe to re-run end-to-end: git fetch && git reset --hard origin/main for source updates, docker/user/dir/SSH creation are all idempotent, and PLATFORM_DOMAIN is RFC-1123-validated before any destructive step. |
| ✅ | Litestream snapshot verification | After enabling the systemd unit, the installer polls litestream snapshots for up to 60s and fails loud if the creds are filled but no snapshot ever lands. |
| ✅ | Traefik default middleware chain | infra/traefik/dynamic.yml ships a default-chain@file bundling gzip compression, security headers (HSTS 1y, nosniff, SAMEORIGIN, strict-origin-when-cross-origin, CSP), and per-IP rate limiting (100 req/s × 200 burst). Apply via traefik.http.routers.<name>.middlewares=default-chain@file. |
| 🚧 | OpenAPI / Swagger UI at /api/docs |
Patch ready in docs/openapi-patch.md. Adds @fastify/swagger + @fastify/swagger-ui to apps/api/src/index.ts using the existing fastify-type-provider-zod schema transform. |
| ✅ | Restore runbook | Step-by-step DR procedure (full VPS rebuild + in-place corrupt-DB recovery + point-in-time forensics) at docs/RESTORE.md. |
| ✅ | Nixpacks builds | Working — auto-detects Python, Node, Go, Rust, Ruby. |
| ✅ | Traefik + Let's Encrypt | Working — automatic cert issuance and renewal. |
| ✅ | Auth (register/login/JWT) | Working. |
| ✅ | Project / deployment CRUD via API | Working. |
| ✅ | Dashboard (login, projects list, project detail with Logs tab) | Working. |
| ✅ | CLI (login + builds gc + logs + env + domains + projects) | Real implementations — login persists creds to ~/.config/deployx. |
See CONTRIBUTING.md for development setup, code conventions, and the pull request process.
Apache 2.0 -- see LICENSE for the full text.





