---
title: "How to set up CI/CD on a VPS with automatic deploys | StreetHosting"
description: "Understand CI, continuous delivery and deployment, then build a simple VPS setup: pipeline, artifact, symlinked releases, health check and rollback."
url: "https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps"
type: "page"
language: "en-US"
---

VPS · 10 min · Intermediate

Published on Sep 28, 2026 · Updated on Sep 28, 2026

# CI/CD on a VPS: from the concept to a pipeline with rollback

CI/CD does not require Kubernetes or an expensive tool. With a pipeline, a releases folder and a health check, an ordinary VPS ships every version safely and rolls back in seconds when something breaks.

By [Equipe StreetHosting](https://streethosting.com.br/en/autores#equipe-streethosting) · StreetHosting infrastructure and support team

[Deploying and running apps](https://streethosting.com.br/en/guides/topics/deploy) [Backup and recovery](https://streethosting.com.br/en/guides/topics/backup) [Automation and webhooks](https://streethosting.com.br/en/guides/topics/automation)

Summarize with:

[](https://chat.openai.com/?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "ChatGPT") [](https://claude.ai/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Claude") [](https://www.google.com/search?udm=50&aep=11&q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Google AI Mode") [](https://x.com/i/grok?text=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Grok") [](https://www.perplexity.ai/search/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Perplexity")

Share:

[](https://x.com/intent/tweet?text=How%20to%20set%20up%20CI%2FCD%20on%20a%20VPS%20with%20automatic%20deploys&url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps "Share on X") [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps "Share on Facebook") [](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps "Share on LinkedIn") [](https://wa.me/?text=How%20to%20set%20up%20CI%2FCD%20on%20a%20VPS%20with%20automatic%20deploys%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fset-up-ci-cd-on-vps "Share on WhatsApp")

For agents: Copy as Markdown [.md](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps.md)

In this guide 7 sections

* [CI, CD and continuous deployment](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#conceitos)
* [The minimal architecture](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#arquitetura)
* [Releases in directories with a symlink](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#releases)
* [Script with health check and rollback](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#script-deploy)
* [Artifact or Docker image](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#artefato-ou-imagem)
* [Webhook, self-hosted runner and Coolify](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#alternativas)
* [Which VPS for CI/CD](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#qual-vps)

Quick answer

**CI/CD on a VPS** means automating the path from commit to production: continuous integration builds and tests every change, and continuous delivery or deployment publishes the result on the server. The simplest architecture that works has a pipeline that produces an artifact, a releases folder on the VPS with a symlink to the active version, a health check after the restart, and rollback by switching the symlink back.

## CI, CD and continuous deployment[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#conceitos)

The three terms show up together and often as synonyms, but each one describes how far the automation goes.

| Practice                    | What is automated                                   | Where it stops                           | When to use it                             |
| --------------------------- | --------------------------------------------------- | ---------------------------------------- | ------------------------------------------ |
| Continuous integration (CI) | Build, lint and tests on every push or pull request | At a green or red result                 | Always, even on a one-person project       |
| Continuous delivery (CD)    | CI plus a ready artifact, deployed to staging       | At a human approval before production    | Teams that want to choose when to publish  |
| Continuous deployment (CD)  | Everything above, including production              | It does not stop: tests pass, it is live | Projects with good tests and fast rollback |

In practice, the CD in the acronym can be either of the last two. What matters is the rule behind it: nothing reaches production without going through the same automated path, and nobody edits files directly on the server. A fix made by hand over SSH makes production drift from the repository, and the next deploy wipes the fix without anyone noticing.

Continuous deployment is only safe with two safety nets: tests that catch the bulk of the errors before publishing, and a way to go back in seconds when something slips through. The rest of this guide builds the second net inside an ordinary VPS.

## The minimal architecture[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#arquitetura)

Five pieces are enough for most projects that run on a single VPS:

* **Repository:** the single source of truth. The main branch represents what is in production.
* **Pipeline:** runs outside the VPS, on GitHub Actions, GitLab CI or another service. It installs dependencies, runs the tests and produces the build.
* **Artifact or image:** the packaged, immutable result of a commit, such as a .tar.gz file or a Docker image tagged with the commit hash. The same package that passed the tests is the one that goes to production, with no new build along the way.
* **VPS:** receives the artifact, unpacks it into a new folder and switches the active version.
* **Health check and rollback:** after the restart, a health endpoint says whether the new version is alive. If it does not respond, the script goes back to the previous version on its own.

Notice what was left out: compiling inside the VPS, running git pull in production, installing development dependencies on the server. All of that lengthens the time the application spends in an inconsistent state. The connection between the pipeline and the server, with a non-root deploy user, secrets and transfer over rsync, is covered in the guide on [deploying with GitHub Actions on a VPS](https://streethosting.com.br/en/guides/vps/github-actions-deploy-to-vps). Here the focus is on what happens inside the server.

## Releases in directories with a symlink[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#releases)

The oldest and most reliable single-server deploy technique is to never overwrite the version that is running. Each deploy gets its own folder, and a symlink named `current` points to the active one:

`/srv/minha-app/ releases/ 20260927101500/ 20260928143000/ shared/ .env uploads/ current -> releases/20260928143000 deploy.sh`

The service always points to the fixed path `/srv/minha-app/current`. Publishing means unpacking into a new folder and moving the symlink; going back means pointing the symlink at the previous folder. No file from the old version is touched, so the rollback depends on neither a build nor a download.

The `shared` folder holds what survives between versions: environment variables, user uploads, persistent cache files. Each release gets links to it, so no deploy wipes production data. The systemd service looks like this:

`[Service] User=deploy WorkingDirectory=/srv/minha-app/current ExecStart=/usr/bin/node /srv/minha-app/current/dist/server.js Restart=on-failure`

The symlink path is resolved when the process starts, so the restart is the exact moment the new version goes live. Until then, the previous one keeps serving normally.

## Script with health check and rollback[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#script-deploy)

The pipeline sends the package to the VPS and calls a script that does the rest. Save it as `/srv/minha-app/deploy.sh`, owned by deploy and with execute permission (`chmod +x`):

`#!/usr/bin/env bash set -euo pipefail APP=/srv/minha-app PACOTE=/tmp/minha-app.tar.gz RELEASE=$(date +%Y%m%d%H%M%S) NOVA="$APP/releases/$RELEASE" ANTERIOR=$(readlink -f "$APP/current" || true) mkdir -p "$NOVA" tar -xzf "$PACOTE" -C "$NOVA" ln -s "$APP/shared/.env" "$NOVA/.env" ln -s "$APP/shared/uploads" "$NOVA/uploads" cd "$NOVA" && npm ci --omit=dev ativar() { ln -sfn "$1" "$APP/current.tmp" mv -T "$APP/current.tmp" "$APP/current" sudo systemctl restart minha-app.service } ativar "$NOVA" for tentativa in $(seq 1 10); do if curl -fsS http://127.0.0.1:3000/health > /dev/null; then echo "Release $RELEASE no ar" ls -1dt "$APP"/releases/* | tail -n +6 | xargs -r rm -rf exit 0 fi sleep 3 done echo "Health check falhou" if [ -n "$ANTERIOR" ] && [ -d "$ANTERIOR" ]; then echo "Voltando para $ANTERIOR" ativar "$ANTERIOR" fi exit 1`

* **Atomic switch:** the new link is created under a temporary name and `mv -T` renames it over the old one in a single operation. At no point does `current` stop existing.
* **Startup window:**the script tries the health check ten times, every three seconds. Adjust it to your application's real startup time.
* **Cleanup:** after a healthy deploy, only the five most recent releases stay on disk. Five instant-rollback versions cover almost every incident.
* **Exit code:** if the health check fails, the script goes back to the previous version and exits with an error. The pipeline job turns red, and you find out without looking at the server.

The restart command needs a sudoers rule that allows only that command for the deploy user, as shown in the GitHub Actions guide. In the workflow, the two final steps look like this:

` - name: Empacotar run: tar -czf minha-app.tar.gz dist package.json package-lock.json - name: Enviar e publicar run: | scp -i ~/.ssh/deploy_key minha-app.tar.gz "$DEST:/tmp/minha-app.tar.gz" ssh -i ~/.ssh/deploy_key "$DEST" /srv/minha-app/deploy.sh`

### What the health check should test[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#o-que-testar)

A `/health` endpoint that returns 200 only when the application can actually serve requests: it connected to the database, read its configuration, loaded what it needs. A health check that always returns 200 only proves the process exists. Outside of deploys, the same endpoint feeds an external monitor such as [Uptime Kuma](https://streethosting.com.br/en/guides/vps/uptime-kuma-vps-monitoring), which alerts you when production goes down between one deploy and the next.

A code rollback does not undo a database migration. If the new version changed the schema, the old one may not work with it. Write migrations that are compatible with both versions: first add columns and tables, ship the code that uses them, and only remove what became obsolete in a later deploy.

## Artifact or Docker image[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#artefato-ou-imagem)

The .tar.gz package works for any interpreted language and for compiled binaries, and it is the path with the fewest moving parts. The alternative is to package the application as a Docker image, which carries the runtime and the system libraries with it. The pipeline builds the image, tags it with the commit hash and pushes it to a registry, such as GitHub's own. On the VPS, the compose file references the tag:

`services: app: image: ghcr.io/sua-org/minha-app:${TAG} restart: unless-stopped env_file: .env ports: - "127.0.0.1:3000:3000" # publishing or going back to a version means changing the tag: # TAG=a1b2c3d docker compose pull && TAG=a1b2c3d docker compose up -d`

* **Rollback:**bring it up again with the previous commit's tag. The old image is still on disk or in the registry.
* **Credentials:** the VPS logs in to the registry with a token that only has permission to read packages.
* **Health check:** still required. The startup command returns before the application is ready, so use the same curl loop after it.
* **Port:** publish on 127.0.0.1 and put Nginx in front, because a port published by Docker bypasses the UFW rules. The configuration is in [Nginx as a reverse proxy on a VPS](https://streethosting.com.br/en/guides/vps/nginx-reverse-proxy-vps).

An image pays off when the application depends on system libraries, when there are several services to coordinate, or when you want the same environment on your laptop and on the server. If you do not have Docker on the VPS yet, start by [installing Docker on Ubuntu](https://streethosting.com.br/en/guides/vps/install-docker-ubuntu-vps).

## Webhook, self-hosted runner and Coolify[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#alternativas)

An external pipeline connecting over SSH is the most common model, but not the only one. The choice changes who starts the deploy and what has to stay open on the VPS.

| Approach                   | Who starts it                                       | Access required on the VPS                    | Good for                                              |
| -------------------------- | --------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------- |
| External pipeline over SSH | GitHub or GitLab runner                             | Open SSH port, key-only login                 | Most projects on a single VPS                         |
| Webhook                    | The VPS itself, when Git sends a notification       | HTTPS endpoint protected by a secret          | Anyone who wants no SSH credential outside the server |
| Self-hosted runner         | An agent on the VPS that fetches jobs from GitHub   | No inbound port, only an outbound connection  | Heavy builds and private repositories                 |
| Coolify                    | Dashboard installed on the VPS and connected to Git | Ports 80 and 443 plus access to the dashboard | Several applications with a UI and automatic SSL      |

### Webhook[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#webhook)

GitHub sends a POST on every push. A small service on the VPS validates the HMAC signature that arrives in the `X-Hub-Signature-256` header, checks the branch and runs the same `deploy.sh`. Without validating the signature, anyone who discovers the URL can trigger deploys. In this model, the script downloads a ready-made artifact or builds on the VPS itself.

### Self-hosted runner[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#runner-self-hosted)

The GitHub Actions agent installed on the VPS opens an outbound connection and runs the jobs locally. It needs no open port and keeps the build close to its destination. GitHub recommends using your own runners only with private repositories, because on a public repository a third-party pull request can run code on your machine. Run the agent as a non-root user and remember that, during a build, it competes with the application for CPU.

### Coolify[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#coolify)

An open source platform you install on the VPS that plays the role of a PaaS: it connects to the repository, builds from the code or from the Dockerfile, publishes to containers, issues the certificate and redeploys on every push. It is the path for anyone who would rather configure through a UI than write a pipeline. The documentation asks for at least 2 cores and 2 GB of RAM, and real builds need more. The step-by-step is in [installing Coolify on a VPS](https://streethosting.com.br/en/guides/vps/install-coolify-on-vps). Anyone who also wants to move the repository away from external services can host Git with [Gitea on the VPS itself](https://streethosting.com.br/en/guides/vps/install-gitea-on-vps), which has an Actions system with syntax compatible with GitHub's.

## Which VPS for CI/CD[](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#qual-vps)

The size of the VPS depends on where the build happens. Compilation is bursty CPU work: it installs dependencies, transpiles, packages. The higher the clock speed, the less time the deploy takes and the less time the application shares the processor with it.

* **Build outside the VPS, in the pipeline:** the server only runs the application. The Ryzen 9 9950X VPS with 2 vCPU, 4 GB DDR5 and 40 GB NVMe, at R$ 66.00, handles a Node API or site with room to spare. To save money, the Xeon VPS with 3 vCPU and 4 GB comes in at R$ 43.00.
* **Build on the VPS, with a self-hosted runner or Coolify:** the Ryzen VPS with 4 vCPU, 8 GB DDR5 and 80 GB NVMe, at R$ 118.00, is a good starting point. With several applications and frequent builds, 6 vCPU and 16 GB at R$ 222.00 keep one deploy from slowing the others down.
* **Many releases and stored images:** count the disk. Five releases of a Node application with dependencies, plus old Docker images, eat up gigabytes fast. The Ryzen plans go from 20 GB to 640 GB of NVMe.

The [Ryzen VPS](https://streethosting.com.br/en/vps/ryzen) line runs on the Ryzen 9 9950X with clock speeds of up to 5.7 GHz, DDR5 memory, NVMe and a 1 Gbps uplink, in São Paulo and with Anti-DDoS included. If the build volume grows, the upgrade is done from the control panel, charges only the prorated difference for the billing cycle and requires just a VM restart. Every option, including the Xeon line, is on the [VPS page](https://streethosting.com.br/en/vps).

Separating environments does not require another machine right away. A second releases folder with another service and another port already works as staging. When production traffic justifies it, move staging to a small VPS and keep the same script on both.

In this guide

* [CI, CD and continuous deployment](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#conceitos)
* [The minimal architecture](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#arquitetura)
* [Releases in directories with a symlink](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#releases)
* [Script with health check and rollback](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#script-deploy)
* [Artifact or Docker image](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#artefato-ou-imagem)
* [Webhook, self-hosted runner and Coolify](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#alternativas)
* [Which VPS for CI/CD](https://streethosting.com.br/en/guides/vps/set-up-ci-cd-on-vps#qual-vps)

## Frequently asked questions

What is the difference between CI and CD?

CI, continuous integration, means automatically building and testing every change pushed to the repository. CD can mean continuous delivery, when the artifact is ready and publishing to production waits for an approval, or continuous deployment, when everything that passes the tests goes straight to production.

Do I need Kubernetes to have CI/CD?

No. A VPS with a releases folder, a symlink and a script with a health check already delivers automatic deploys with rollback in seconds. Kubernetes solves a different problem: orchestrating many containers spread across several servers.

How do I roll back a deploy on a VPS?

With releases in separate directories, just point the current symlink at the previous folder and restart the service, which takes seconds. With Docker images, the equivalent is bringing up the previous commit's tag again. Keep in mind that reverting the code does not undo migrations already applied to the database.

Is a self-hosted runner safe on a VPS?

It is reasonable on a private repository, with the agent running under a non-root user and with no access to sensitive data beyond what it needs. On a public repository, GitHub itself advises against it, because third-party pull requests can end up running code on your server.

Does Coolify replace a CI/CD pipeline?

For many projects, yes: it builds, publishes to containers, issues the certificate and redeploys on every push. Teams that already have a substantial test suite usually keep CI on GitHub Actions and let Coolify handle only the publishing.

Next step

See Ryzen VPS

Ryzen 9 9950X VPS in São Paulo with root access, NVMe and gamer Anti-DDoS.

[See Ryzen VPS](https://streethosting.com.br/en/vps/ryzen)

[See VPS plans Root VPS in Brazil with NVMe and Anti-DDoS.](https://streethosting.com.br/en/vps) [See Xeon VPS Xeon VPS for steady workloads, automation and long-running projects.](https://streethosting.com.br/en/vps/xeon)

## Related guides

[VPS Intermediate GitHub Actions deploy to VPS: step-by-step pipeline Every push to the main branch can build, upload and restart your application on the VPS without anyone opening a terminal. Here is how to set up that flow with a non-root deploy user, well-guarded secrets and protection against simultaneous deploys. 10 min Read guide](https://streethosting.com.br/en/guides/vps/github-actions-deploy-to-vps) [VPS Intermediate How to install Coolify on a VPS and deploy with HTTPS Coolify turns an empty VPS into a deploy platform with builds, a proxy, certificates and rollback. Here is the install, the firewall details Docker forces on you and the path from repository to a domain with HTTPS. 9 min Read guide](https://streethosting.com.br/en/guides/vps/install-coolify-on-vps) [VPS Intermediate How to install Docker on Ubuntu 22.04 or 24.04 on a VPS (straight to the point) A stable Docker install on an Ubuntu VPS depends on the right package source and step by step validation. In this guide you set up the official repository, install Engine and Compose, test the daemon and apply basic security measures before deploying applications. 3 min Read guide](https://streethosting.com.br/en/guides/vps/install-docker-ubuntu-vps)

[← Back to the Guide Center](https://streethosting.com.br/en/guides)
