Applications
Docker, systemd and Compose apps: sources, ports, health checks and resource limits.
An application is a long-running service on one server, built from a Git repository or pulled from a registry. Each successful deployment becomes a release you can roll back to.
Runtimes
Pick how the app runs when you create it. It can't be changed later; create a new app to switch.
systemd and Compose apps deploy from Git and need agent 1.1.0 or later on the server.
systemd (native) apps
- The stack is detected the same way as Docker builds (npm, pnpm, Yarn; pip, Poetry, uv, Pipenv). If the server's Node.js major version doesn't match what the project asks for, the right one is downloaded from nodejs.org and checksum-verified. Python uses the server's
python3with a virtualenv per release. - Your app must listen on
$PORT, which VPSPilot sets to the app's public port. Bind to0.0.0.0so the domain proxy can reach it. - Builds run as the app's own user in a sandboxed, time-limited systemd unit; your repository's scripts never run as root.
- Each release is a new folder; files written there are gone after the next deploy. Keep uploads and SQLite files in
$VPSPILOT_SHARED_DIR, which survives releases. - The service is hardened (read-only system, private
/tmp, no new privileges). Logs go to the systemd journal.
Compose apps
- VPSPilot finds
compose.yaml(ordocker-compose.yml) in the root directory, or the file you name. With more than one service, name the public service that receives traffic and its port. - Refused for safety: privileged mode, added capabilities, devices, host networking or namespaces, security options, bind mounts outside the repository, the Docker socket, and external volumes or networks.
- Published
ports:and fixedcontainer_namevalues are ignored (with a warning), since they would clash with other apps. VPSPilot publishes the public service itself and joins it to the private network asvp-<name>. - The app's variables are available for
${VAR}substitution in the file and are set on the public service. Named volumes keep their data across deploys and aren't deleted when the app is removed.
Sources
Git repository
Pick a repository from your connected GitHub App, or paste any public HTTPS clone URL. Choose a branch, which defaults to main. Each deploy makes a shallow checkout of that branch, or of a specific branch, tag or commit you ask for.
Container image
Give an image reference such as ghcr.io/acme/api:1.4.2. VPSPilot pulls it and runs it; there is no build step. Deploying a different tag is a deploy with a new image reference.
Settings
namestringruntimedocker · systemd · composecompose_file / compose_servicepath / namecontainer_port1–65535PORT.host_port1024–65535dockerfilepath · Dockerfilebuild_contextpath · .build_command / start_commandstringhealth_pathpath · /health_timeout_seconds5–600 · 60data_pathpath/app/uploads. See below.release_commandstringnpx prisma migrate deploy. See below.private_portbool · falsepreview_domaindomainerror_ignorelist of textcpu_limite.g. 0.5, 2memory_limite.g. 512m, 2gauto_deploybool · trueNetworking
The app joins the private vpspilot Docker network, where the domain proxy reaches it as vp-<name>:<container_port> and where it can reach managed databases by name. Its host port is also published on all interfaces, which is handy for testing.
Once an app has a domain, traffic goes through Caddy on 80 and 443. Use a firewall such as ufw to block the app’s host port from the internet if you don’t want it reachable directly.
systemd apps listen directly on their public port on the server; the domain proxy reaches them through the private network's gateway (and VPSPilot opens that path in ufw if it's on).
Files that survive deploys
Every deploy starts the app fresh, so anything it writes inside its container or release folder is gone after the next one. Keep uploads, SQLite files and the like where VPSPilot keeps them:
Removing an app keeps its data volume; delete it with docker volume rm when you're sure. See app file backups.
Release command
Set a release command for anything that must happen before a new version serves: database migrations, cache warm-ups. It runs once per deploy with the new code and the app's environment (a one-off container of the new image, the new release as the app's user, or a one-off container of a Compose app's public service), after the build and before the candidate starts. If it fails, the deploy stops and the live version keeps serving. Rollbacks don't run it again. Needs agent 1.1.5 or later.
Run a command
The app page's Run a Command box runs a one-off command where the app runs, with its environment (for example npm run seed or creating an admin user), and streams the output. It stops after 30 minutes.
Scheduled jobs
Run a command inside an app on a schedule, such as npm run cleanup every night, from the app's Scheduled Jobs section. It runs where the app does: in its container (Docker), its public service (Compose), or as the app's own user in its live release with its environment (systemd), never on the server as root.
- Schedules are cron expressions (
0 2 * * *) or@hourly,@daily,@weekly,@monthly, in the timezone you pick. - A run is skipped while the previous one is still going, and stops after its time limit (1 hour by default, up to 6).
- Failed runs notify the inbox and alert channels that subscribe to Scheduled job failed. Each run's output is kept like any job's; Run now starts one straight away.
Needs agent 1.1.4 or later.
Preview deployments
With a preview domain set (GitHub-linked apps), every pull request from the repository gets its own copy of the app,<app>-pr-<n>, at pr-<n>.<preview domain>. It's redeployed on every push to the pull request and removed when it closes.
- Add one DNS record,
*.preview.example.compointing at the server, and accept the GitHub App's new Pull requests (read) permission and event in its GitHub settings. - Previews get the app's variables, including its database. Use Overrides for previews to point them at a staging database or test keys.
- Pull requests from forks never get a preview: they would run someone else's code with your app's secrets.
- Up to 10 previews per app; their ports are private, so they're only reachable through their domains.
Moving an app to another server
Move to Another Server on the app page copies the app to a server you pick, step by step: its files (through your off-server backup storage), each managed database attached to it (a new instance on the new server, the data exported and imported, the app's variable pointed at it; Redis data isn't copied), a deploy there, and its domains on the new server's proxy.
The old copy keeps serving the whole time. When the move is ready, the page lists your domains with whether DNS already points at the new server. Change the records, then Finish move: the old copy is removed. The old server's databases are kept until you delete them. Cancel, or any failed step, puts the app back on the old server as it was. Do it at a quiet time: writes to a database after its copy is taken aren't moved.
Apps keep running
Containers run with Docker’s unless-stopped restart policy and systemd apps with Restart=always, so they survive reboots and restart after crashes. The Application down alert tells you if one stays down.
A Docker or Compose app that connects to localhost:5432 reaches itself, not a database on the server. Use a managed database (reachable by name, e.g. vpdb-main) and attach it, or a systemd app, which does share the server's localhost.