Deployment guide

Jenkins on a VPS: your open-source CI/CD server with Docker

Deploy on a VPS Cloud →

Tutorial

Jenkins on a VPS: your open-source CI/CD server with Docker

Self-hosting7 min read4 steps

The build minutes billed by hosted CI services eventually add up, and your deployment secrets live with a third party. Jenkins, the open-source automation server under the MIT licence, does the same work on your own server: it pulls the code on every push, runs the build and the tests, then deploys. This guide installs Jenkins LTS with Docker Compose on a VPS, publishes it over HTTPS behind nginx, writes a first pipeline and sets the security rules that matter.

Contents· Why host your CI/CD with Jenkins1/11
  1. 01Why host your CI/CD with Jenkins
  2. 02What Jenkins brings
  3. 03Jenkins versus other self-hosted CI tools
  4. 04Prerequisites
  5. 05Install Jenkins with Docker Compose
  6. 06Do not run builds on the controller
  7. 07Write a first pipeline
  8. 08Credentials and build agents
  9. 09Back up and update Jenkins
  10. 10Troubleshooting
  11. 11One-click install from the ServOrbit Marketplace

Why host your CI/CD with Jenkins

A hosted CI is convenient until builds multiply: every minute beyond the quota is billed, large projects wait their turn, and the deployment keys of your servers are stored at the provider. Jenkins reverses this model. It runs on your VPS, executes as many builds as your machines allow and keeps your credentials encrypted at home.

It is also the tool most teams already know: a Jenkinsfile reads without training, documentation is plentiful, and almost every tool on the market has its Jenkins plugin.

What Jenkins brings

  • Pipelines as code — a Jenkinsfile versioned with the project describes every stage, reviewed in code review like the rest.
  • Git webhooks — GitHub, GitLab, Gitea or Forgejo trigger the build on every push or pull request.
  • Multibranch — every branch and every pull request gets its own pipeline, created automatically.
  • Build agents — builds run on dedicated machines, connected over SSH.
  • Over 2,000 plugins — Docker, Kubernetes, SonarQube, Slack, JUnit test reports.
  • Encrypted credentials — SSH keys and tokens injected into the pipeline, masked in the logs.

Jenkins versus other self-hosted CI tools

Scroll the table

JenkinsWoodpecker CIOneDev
LicenceMITApache-2.0MIT
Pipeline definitionJenkinsfile (Groovy)YAMLYAML or visual editor
Built-in Git forgeNo, plugs into yoursNo, plugs into yoursYes
Plugin ecosystemOver 2,000 pluginsPlugins as Docker imagesBuilt-in features
Best forComplex pipelines, existing teamsSimple container pipelinesSmall team wanting all-in-one

Prerequisites

An Ubuntu 24.04 VPS with 2 GB of RAM and 2 vCPUs is enough for the controller; plan 4 GB if your builds run on the same machine. You need Docker and the Compose plugin, a domain name (or a subdomain) pointing to the VPS, and nginx with a TLS certificate to publish it over HTTPS. Jenkins must answer on a stable HTTPS address: it is the one your repositories call for webhooks.

Install Jenkins with Docker Compose

  1. Create the Compose file

    This docker-compose.yml starts Jenkins LTS on Java 21. The port is only published on the loopback interface: only nginx reaches it. All data (jobs, plugins, credentials, history) lives in the jenkins-home volume. Port 50000 for inbound agents is not published: agents connect over SSH.

    services:
      jenkins:
        image: jenkins/jenkins:2.580.1-lts-jdk21
        restart: unless-stopped
        ports:
          - "127.0.0.1:8104:8080"
        environment:
          JAVA_OPTS: -Djava.awt.headless=true
        volumes:
          - jenkins-home:/var/jenkins_home
        healthcheck:
          test: ["CMD-SHELL", "curl -fsS -o /dev/null http://localhost:8080/login || exit 1"]
          interval: 15s
          retries: 10
          start_period: 60s
    volumes:
      jenkins-home: {}
  2. Start the container

    Put the file in /opt/jenkins, then start the stack. The first start takes about thirty seconds; docker compose ps must show the healthy state.

    mkdir -p /opt/jenkins && cd /opt/jenkins
    docker compose up -d
    docker compose ps
  3. Publish Jenkins over HTTPS behind nginx

    In the server block of your domain, forward the X-Forwarded-* headers: without them, Jenkins generates HTTP links and reports a broken reverse proxy.

    location / {
        proxy_pass http://127.0.0.1:8104;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port 443;
    }
  4. Unlock Jenkins

    Open https://your-domain. Jenkins asks for the initial password written to its data volume:

    docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

    Then install the suggested plugins, create your administrator account and confirm the instance's HTTPS URL.

Do not run builds on the controller

The Jenkins controller holds all your credentials. A build running on it can read them. In Manage Jenkins, set the number of executors of the built-in node to 0 and connect at least one agent over SSH. For the same reason, do not mount the VPS's Docker socket into the Jenkins container: it would give any build root access to the server.

Write a first pipeline

Create a "Multibranch Pipeline" pointing to your repository, then add a Jenkinsfile at the root. This example builds and tests every branch, and only deploys the main branch, on an agent labelled linux:

pipeline {
    agent { label 'linux' }
    stages {
        stage('Build') { steps { sh 'make build' } }
        stage('Test')  { steps { sh 'make test' } }
        stage('Deploy') {
            when { branch 'main' }
            steps { sh './deploy.sh' }
        }
    }
}

Finally, add a webhook in your repository to https://your-domain/github-webhook/ for GitHub, or to the URL given by your forge's plugin. Every push then triggers a build.

Credentials and build agents

SSH keys, API tokens and deployment passwords go into Manage Jenkins, Credentials. Jenkins stores them encrypted in its volume and only injects them into a build when a step asks for them, with withCredentials or the environment directive of a Jenkinsfile; their value is masked in the logs. Give each credential the narrowest possible scope: one deployment key per target server, a read-only token to clone repositories.

For agents, the simplest option is a second VPS with Java and Docker installed. In Manage Jenkins, Nodes, add a permanent node with the "via SSH" launch method, the VPS address and an SSH key saved as a credential. Give it a label, for example linux, and target it in your pipelines. The controller then only orchestrates, and a compromised build never reaches your other secrets.

Back up and update Jenkins

The whole state of Jenkins lives in the jenkins-home volume: jobs, configuration, plugins, encrypted credentials and build history. An archive of this volume is enough to restore everything. Stop Jenkins during the copy to get a consistent archive:

cd /opt/jenkins
docker compose stop jenkins
docker run --rm -v jenkins_jenkins-home:/data -v \"$PWD\":/backup alpine tar czf /backup/jenkins-home.tar.gz -C /data .
docker compose start jenkins

To update, follow the LTS line, released roughly every twelve weeks with intermediate fixes: replace the image tag in docker-compose.yml, then run docker compose pull and docker compose up -d. Then update the plugins from Manage Jenkins, Plugins, and read the security advisories published by the project: most fixed vulnerabilities concern plugins, not the core.

Troubleshooting

"It appears that your reverse proxy set up is broken": nginx does not forward X-Forwarded-Proto or the Jenkins URL is not HTTPS. Fix the location block, then the URL in Manage Jenkins, System.

The container keeps restarting: read the logs. The most frequent cause is a lack of memory; give the VPS at least 2 GB of RAM.

docker compose logs --tail=100 jenkins

Webhooks do not arrive: check the webhook's delivery history in the repository, then that the address called is the public HTTPS URL of Jenkins.

One-click install from the ServOrbit Marketplace

On a ServOrbit VPS, Jenkins installs from the Marketplace: choose the address, click Install, and the script deploys Jenkins LTS in Docker, with its data volume and the nginx reverse proxy over HTTPS. The initial administrator password is shown in your client area. No domain? The VPS's free subdomain is enough, HTTPS included.

Your Jenkins CI/CD server, on your own VPS

A ServOrbit Ubuntu 24.04 VPS with 2 GB of RAM is enough to start Jenkins. One-click install from the Marketplace, HTTPS on your domain or on the VPS's free subdomain, with no build-minute quota.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab