Deploy Apps to Your Own VPS: The 2026 Deployment Ladder
Choose how to deploy apps and Next.js to a VPS, from systemd and Docker Compose to self-hosted PaaS, with practical CI, rollback, and recovery guardrails.

On this page
- Choose the lowest rung that solves your deployment problem
- Keep a simple app under systemd
- Use Docker Compose when packaging is the recurring problem
- Choose a self-hosted PaaS only when its workflow earns its upkeep
- Deploy Next.js as a production application
- Automate a release that already has a recovery path
- Give Claude Code bounded deployment work
- Rehearse one release before moving real traffic
- FAQ
Choose between owning a small deployment script and running a self-hosted platform. We recommend Ubuntu 24.04 LTS with systemd for a simple app, or Docker Compose when containers already fit your workflow. Add a self-hosted PaaS when a shared dashboard saves recurring work. Automate a proven release procedure through CI, keep the previous artifact, and verify database compatibility before promising rollback.
Choose the lowest rung that solves your deployment problem
Your choice is how much deployment machinery you want to maintain alongside your application. We recommend starting on a single Ubuntu 24.04 LTS VPS with systemd for a straightforward app, or Docker Compose if you already package your app in containers.
A deployment ladder describes increasing operational scope, not a career progression. You can run a useful business indefinitely without a deployment dashboard or a cluster. Move up when a repeated problem justifies the additional software you will need to repair.
| Approach | Choose it when | Work you still own |
|---|---|---|
| Static files behind a web server | The app needs no server runtime | Artifact upload, cache behavior, recovery |
| systemd and a reverse proxy | A simple app needs a persistent process | Release script, runtime maintenance, rollback |
| Docker Compose | Container packaging already fits your app | Image delivery, persistent data, traffic switching |
| Coolify or Dokploy | A shared deployment dashboard removes recurring work | Platform upgrades, access control, recovery |
| Kamal | You want container releases controlled through configuration and commands | Application health, database compatibility, server maintenance |
| Kubernetes | You have specific scheduling requirements and people to operate it | Cluster lifecycle and application operations |
CI belongs alongside every rung: it decides when and how your release procedure runs. Adding GitHub Actions does not require changing how your app runs on the server. Equally, installing a dashboard does not remove responsibility for a full disk or a broken database backup.
Choose the hosting environment after deciding whether builds will run on the production machine. Builds need spare capacity while the existing application continues serving requests. If you are still choosing a provider, use the Next.js hosting comparison to narrow the options around your deployment model.
Keep a simple app under systemd
For an app with a single long-running web process, we would start with systemd and a reverse proxy. The proxy handles public HTTPS traffic and forwards requests to the app on a private listener. systemd starts the process after boot and can restart it after a crash.
Keep each release in its own directory rather than editing the directory serving production. Build and validate the candidate before changing the active release. Record the source commit with the artifact so that an incident investigation starts with an exact identity.
Run the application as a dedicated unprivileged account, such as deploy, with only the filesystem access it needs. Store production secrets outside the repository in a restricted file loaded by the service. Keep uploaded files and database storage outside release directories so replacing application code cannot replace customer data.
A release script should stop when preparation fails and retain the previous working release. After switching the active directory, restart the service and check the public application path. A symlink change alone does not replace an already running process, and a service restart can interrupt requests.
Accept that interruption explicitly if the application can tolerate it. If it cannot, you need overlapping app processes and controlled proxy switching, with enough spare capacity to run both. Do not turn a small shell script into a homegrown deployment platform merely to avoid choosing a tool.
Use Docker Compose when packaging is the recurring problem
Choose Docker Compose when you want the application runtime packaged with the code, especially if your development workflow already uses containers. It gives you a reviewable definition of the services and their connections. It still leaves you responsible for deciding when a replacement container should receive production traffic.
We recommend building the image outside the production VPS and deploying an immutable image digest. Preserve the image for the previous release in your registry, with credentials that remain available during recovery. Rebuilding an old commit during an outage can produce different dependencies or fail because an external service is unavailable.
Treat database volumes and uploaded files as persistent infrastructure. Document where they live and how they are backed up independently of container replacement. A volume on the same VPS survives container recreation, but it does not survive losing that VPS.
Publish only the ports that must be reachable from outside the container network. If a host reverse proxy fronts the app, bind its published application port to loopback; keep the database off public interfaces. Verify reachability from outside the server because Docker's networking rules can make assumptions based only on a host firewall misleading.
A restart policy can restart an exited process, but it cannot decide whether a release serves correct responses. A container marked unhealthy is also not automatically repaired by plain Compose just because a health check exists. Work through the Docker Compose production checklist before treating your development configuration as a production deployment.
Choose a self-hosted PaaS only when its workflow earns its upkeep
A self-hosted PaaS makes sense when deployments repeatedly require someone to translate application requests into server changes. A shared interface can reduce that coordination burden, especially when teammates should deploy without learning every host convention. You also acquire an administrative application whose compromise or failure can affect the workloads it manages.
Coolify provides deployment workflows for containerized applications and databases, with service templates documented in the Coolify documentation. We would shortlist it when the desired experience starts with connecting an application and managing it through a dashboard. Evaluate the recovery path as carefully as the initial deployment.
Dokploy documents application deployments and Docker Compose management in its official documentation. We would include it when an existing Compose definition is central to your workflow. Neither product deserves selection from screenshots alone; use a disposable app to check how configuration changes and failed releases behave.
Kamal is a different choice: a deployment tool for containerized web applications rather than a shared application dashboard. Its project documents rolling restarts and asset bridging among its deployment features. We would choose this direction when reviewed configuration and command-driven releases fit the team better than a control panel. See the Kamal project.
Before choosing, verify which resources the platform owns and how you would recover them on a replacement machine. Back up platform configuration as well as application data, and protect administrator access accordingly. Use the Coolify, Dokploy, and Kamal comparison to make the workflow decision before installing anything on your production host.
Deploy Next.js as a production application
To deploy Next.js to a VPS, first decide whether the application needs a server runtime. A static export can suit an application whose required features work without request-time server execution. Otherwise, package a production build and run its production server, never the development server.
Put a reverse proxy in front of the Next.js process. Browser-exposed NEXT_PUBLIC_ values are embedded during the build, so changing a runtime environment file does not replace them in an existing browser bundle. Review these deployment behaviors in the Next.js self-hosting documentation.
We prefer to start with a single application instance because it reduces the number of deployment interactions you must understand. When introducing overlapping instances, account for cache coordination and Server Action compatibility. Keep old static assets available during release transitions so an already open browser tab can still fetch what its page expects.
Build against the same operating-system family and processor architecture as the deployment target when native dependencies are involved. Keep build credentials separate from runtime credentials, and do not grant the build unrestricted production database access just because generating a page needs data. Review which routes fetch data during the build before wiring the pipeline to production services.
Your acceptance check should exercise a real user action rather than only request the home page. For an authenticated app, that might mean signing in and completing a harmless write using a dedicated account. Include any streamed response or upload behavior your application depends on, because a healthy process can still sit behind a misconfigured proxy.
Automate a release that already has a recovery path
Make the release procedure reliable before attaching a push trigger. CI should invoke the same reviewed procedure you can run during an incident. Keep deployment orchestration in the repository so the current behavior is visible without reconstructing dashboard settings.
- Build an identifiable artifact from reviewed source and run the application checks before delivery.
- Serialize production deployments so competing jobs cannot switch the active release out of order.
- Give the deployment identity narrowly scoped access, with host identity verification for SSH connections.
- Apply compatible database migrations through a controlled step that runs once per deployment.
- Verify the candidate before traffic switching, then check the public application path afterward.
- Retain the previous artifact and record whether recovery completed if the release fails.
A database change is the usual boundary of automatic rollback. Prefer additive changes that allow both the old and new application code to operate during a transition. Remove obsolete fields only after the older code is retired and your recovery plan no longer depends on it.
Restoring a database backup can discard writes accepted after that backup, so it is a different operation from switching application releases. Write down who can authorize that recovery and what happens to incoming traffic while it runs. For a migration that cannot coexist with the previous application, plan a maintenance window or a forward repair instead of claiming automatic rollback.
Uninterrupted releases require enough capacity for the old and new application to overlap, plus readiness checks and connection draining. Long-lived connections and background workers need explicit behavior during that transition. The GitHub Actions deployment guide covers that handoff; a successful CI job alone does not establish that users experienced uninterrupted service.
Give Claude Code bounded deployment work
We recommend Claude Code for drafting deployment changes and investigating operational failures within explicit boundaries. Give it the service definition and release script, together with sanitized logs relevant to the failure. Ask for an explanation tied to that evidence before authorizing a change to production.
Keep the routine deployment interface narrow: a reviewed script with predictable inputs is easier to supervise than unrestricted shell access. Allow investigation separately from changes to production state. Database deletion and changes to SSH access should require a deliberate approval outside the routine deploy path.
Enforce these boundaries through accounts and permissions, not just an instruction in a chat. Broad sudo access or access to the Docker socket can defeat an otherwise careful restriction. Avoid putting production secrets into prompts or verbose build output, and treat repository text and logs as untrusted input.
Require a useful completion report: which artifact is active and what application checks succeeded. If a check fails, the agent should stop or invoke a previously authorized recovery procedure rather than inventing a repair under pressure. Human review is most valuable at the boundary where the action can change persistent data or remove access.
Rehearse one release before moving real traffic
Start with a disposable application on Ubuntu 24.04 LTS and the deployment approach you selected. Establish working administrative access before installing the application stack. Follow the first-hour Ubuntu server setup, then confirm you can open a separate SSH session without relying on the original connection.
Deploy a release with an identifiable marker, then deploy another with a changed marker using the same procedure. Exercise the public HTTPS path and a representative application action after each deployment. Keep build output and deployment records so you can connect the visible result to the artifact that produced it.
Switch back to the previous artifact and repeat the application check. Separately restore a backup into an isolated database and confirm the application can read the recovered data. Perform a planned reboot of this disposable environment to verify that the services return without a remembered shell command.
Record the recovery procedure beside the deployment script and assign someone to respond when deployment checks fail. Connect the CI trigger only after this rehearsal passes. Your next production change should use that same procedure with its artifact identity recorded before traffic moves.
FAQ
What is the simplest way to deploy an app to a VPS?
For a static site, publish the generated files behind a web server. For a dynamic app, use a dedicated service account and a systemd service behind a reverse proxy. Keep deployment steps in a script from the beginning. Add container tooling when packaging or coordinating dependencies becomes harder than maintaining the basic service.
Can I deploy Next.js to a VPS without Vercel?
Yes. The decision is mostly about who operates the surrounding infrastructure. On your VPS, you own the public entry point and the release process, including recovery when either fails. Check any platform-specific integrations before moving, and rehearse an actual user workflow on the new host before switching production traffic.
Which self-hosted PaaS should a small team choose?
We would shortlist Coolify and Dokploy, then choose through a disposable deployment that resembles your application. Give more weight to understandable configuration and recoverable state than to the initial setup screen. If the team prefers repository-controlled deployment commands and does not need a shared dashboard, evaluate Kamal as a separate approach.
Can Docker Compose deploy an application without downtime?
Plain container replacement can interrupt requests. Keeping service available during a release requires a candidate instance that becomes ready before traffic moves, followed by draining the previous instance. Your application must also tolerate overlap, including database access and background work. Compose can participate in that design, but a normal update command does not establish it.
Do I need Kubernetes to run several apps on my own servers?
An application count alone is a poor reason to adopt Kubernetes. Separate services or Compose projects can be easier to operate when placements are predictable and a small team owns the whole fleet. Consider a cluster when concrete scheduling or isolation requirements justify it, and when someone can maintain its control plane and recovery procedures.
Put this guide in the bigger picture
See how this topic connects to the rest of your product. Our free field guide explains apps, infrastructure and AI, with checklists to help you make business decisions.






