Docker compose
This page describes how to set up a self-managed instance of the Plumber Platform using Docker Compose, on a single Linux host.
💻 Requirements
On top of the common requirements (GitLab version, network access, DNS), this method needs a Linux server running the stack in 🐳 Docker containers:
- OS: Ubuntu or Debian (tested and supported)
- Hardware: CPU x86_64/amd64 with at least 2 cores, 4 GB RAM, and 100 GB of storage for Plumber
- Installed software:
⚠️ Windows / WSL
Plumber’s install scripts target native Linux (Ubuntu, Debian). WSL (Windows Subsystem for Linux) has worked for some users, but we do not officially support it: depending on your WSL setup, how you cloned the repository and your Git settings, you may have to fix the scripts by hand. On a CRLF error (for example bad interpreter: /bin/bash^M, or a stray \r), convert the line endings with dos2unix.
🚀 Installation
1. Configure your DNS
Create a DNS A record pointing your chosen domain to your server’s public IP address:
- Name:
<your_plumber_domain>(e.g.plumber.mydomain.com) - Type:
A - Content:
<your-server-public-ip>
2. Setup Plumber
Run the interactive installer on your server:
curl -fsSL https://raw.githubusercontent.com/getplumber/plumber-platform/main/install.sh | bashPick Production as the deployment type. The installer then asks for:
- Plumber domain name, for example
plumber.mydomain.com. The installer checks that it resolves and warns you (without stopping) if the DNS record is not in place yet. - GitLab instance URL, for example
https://gitlab.mydomain.com. - Connection scope: the whole GitLab instance (the OAuth application is created by an instance Admin) or one root group (the application is created in that group, and you give its exact path, the
full_pathas GitLab shows it). - GitLab OAuth application: the installer prints the link to the right Applications page and the exact values to fill in, including the redirect URI
https://<your_plumber_domain>/api/v1/auth/callback, then asks for the Application ID and the Secret. - Plumber component (optional): copy the CI/CD component into a project of your GitLab and publish it to your CI/CD catalog (see below). If you accept, the installer asks for the target group and a token from an Owner of that group, used for this run only.
- TLS certificate method: Let’s Encrypt (the server must be reachable from the internet), or your own certificates, which you place at
.docker/traefik/certs/plumber_fullchain.pemand.docker/traefik/certs/plumber_privkey.pem. - Custom Certificate Authority: answer yes if your GitLab instance (or your own Plumber certificate) is signed by a private CA, then drop the root CA files (
.pemor.crt) into.docker/ca-certificates/. - Database: the bundled PostgreSQL container, or your own external instance (host, port, user, database name, password, SSL mode, timezone).
Answer yes to Start Plumber now? (the first boot runs the database migrations, so it takes a moment), then open https://<your_plumber_domain> and sign in with GitLab as a Plumber Admin to finish the setup. If you asked for the component copy, the installer ends with Component published and the value to set in Settings; otherwise it says why and how to run the step again.
Caution
The installer refuses to run when a .env already exists in the directory. That file holds PLUMBER_TOKEN_ENCRYPTION_KEY, which seals every stored GitLab secret and cannot be rotated in place. To upgrade an existing install run ./scripts/update.sh instead.
Info
You can also clone the repository first and run the installer locally:
git clone https://github.com/getplumber/plumber-platform.git plumber-platformcd plumber-platform./install.shStep 1: Setup your environment
Clone the repository on your server
git clone https://github.com/getplumber/plumber-platform.git plumber-platformcd plumber-platformCreate your configuration file
cp .env.example .envThe image version is not in .env: both image tags are pinned in compose.yml, and a release moves them together.
Step 2: Configure the domain name and the GitLab URL
Edit .env and set DOMAIN_NAME and GITLAB_URL:
DOMAIN_NAME="<your_plumber_domain>"GITLAB_URL="https://<url_of_your_gitlab_instance>"DOMAIN_NAME="plumber.mydomain.com"GITLAB_URL="https://gitlab.mydomain.com"Info
The connection scope (whole instance or one root group) is not an .env setting in v2: it is part of the GitLab connection you configure once with plumber-bootstrap in step 7, and it is stored in the database.
Step 3: Generate the secrets
perl -pi -e "s|^PLUMBER_TOKEN_ENCRYPTION_KEY=.*|PLUMBER_TOKEN_ENCRYPTION_KEY=\"$(openssl rand -hex 32)\"|" .envperl -pi -e "s|^PLUMBER_DB_PASSWORD=.*|PLUMBER_DB_PASSWORD=\"$(openssl rand -hex 16)\"|" .envperl -pi -e "s|^PLUMBER_REDIS_PASSWORD=.*|PLUMBER_REDIS_PASSWORD=\"$(openssl rand -hex 16)\"|" .envThese commands edit .env in place and leave no backup file behind.
Danger
PLUMBER_TOKEN_ENCRYPTION_KEY must be exactly 64 hexadecimal characters (openssl rand -hex 32). It seals every GitLab secret and token the Platform stores, it cannot be rotated in place, and without it a database backup is unreadable. Back up .env.
Step 4: Choose your deployment profile
Two variables select the stack: COMPOSE_PROFILES (which optional services run) and CERT_RESOLVER (the Traefik resolver used for certificates).
COMPOSE_PROFILES | CERT_RESOLVER | Meaning |
|---|---|---|
letsencrypt,internal-db | le | Let’s Encrypt certificates and the bundled PostgreSQL |
custom-certs,internal-db | (empty) | Your own certificates and the bundled PostgreSQL |
letsencrypt or custom-certs | as above | External PostgreSQL: drop internal-db and set the PLUMBER_DB_* variables |
Option A: Let’s Encrypt (automatic certificates)
COMPOSE_PROFILES="letsencrypt,internal-db"CERT_RESOLVER="le"No further action needed: certificates are issued at first launch, and Traefik redirects port 80 to port 443.
Option B: Custom certificates
COMPOSE_PROFILES="custom-certs,internal-db"CERT_RESOLVER=""Copy your certificate files:
cp /path/to/your/fullchain.pem .docker/traefik/certs/plumber_fullchain.pemcp /path/to/your/privkey.pem .docker/traefik/certs/plumber_privkey.pemExternal PostgreSQL
Remove ,internal-db from COMPOSE_PROFILES and configure the connection in .env:
PLUMBER_DB_HOST="your-db-host"PLUMBER_DB_PORT="5432"PLUMBER_DB_USER="plumber"PLUMBER_DB_NAME="plumber"PLUMBER_DB_SSLMODE="disable" # disable, allow, prefer, require, verify-ca, verify-fullPLUMBER_DB_TIMEZONE="UTC"PLUMBER_DB_PASSWORD stays the password of that database user. Every other backend variable has a default and is documented in the Configuration Reference.
Step 5: (Optional) Add your custom CA
If your GitLab instance (or your own Plumber certificate) uses a TLS certificate signed by your own Certificate Authority, drop the CA certificate files (.pem or .crt) into .docker/ca-certificates/ and build the bundle:
cp /path/to/your/ca.pem .docker/ca-certificates/ca.pem./scripts/ca-bundle.shThe script validates each certificate, concatenates them into .docker/ca-certificates/plumber-ca.bundle, sets PLUMBER_PROVIDER_CA_BUNDLE in .env (the backend reads one PEM path) and prints the command to restart the apps. The frontend trusts the same directory file by file. Both containers therefore trust your CA.
Step 6: Check and launch
./scripts/preflight.shdocker compose up -dpreflight.sh validates the host (Docker, Compose version, ports) and the configuration: the required variables, the 64 hex characters of the encryption key, the CA bundle consistency, a valid TLS profile, the DNS record, the certificate files, and that GitLab is reachable. Fix what it reports before starting.
Step 7: Configure the GitLab connection
Create the GitLab OAuth application first (redirect URI https://<your_plumber_domain>/api/v1/auth/callback), then run plumber-bootstrap once, inside the backend container:
read -rs -p "OAuth application secret: " PLUMBER_BOOTSTRAP_CLIENT_SECRET; echo; export PLUMBER_BOOTSTRAP_CLIENT_SECRETdocker compose exec -T -e PLUMBER_BOOTSTRAP_CLIENT_SECRET backend plumber-bootstrap \ -base-url https://gitlab.mydomain.com -client-id <application-id> -scope instanceThe secret is read from your shell and passed with the name-only form of -e: docker compose exec -e PLUMBER_BOOTSTRAP_CLIENT_SECRET forwards the value of the exported variable without ever putting it on the command line, so it stays out of your shell history and out of the process list.
To connect one root group instead of the whole instance, the same run needs a GitLab access token that is Maintainer or above on that group: plumber-bootstrap refuses a group without one. Pass it the same way as the secret, and it becomes the access token Plumber syncs with:
read -rs -p "GitLab access token: " PLUMBER_BOOTSTRAP_TOKEN; echo; export PLUMBER_BOOTSTRAP_TOKENdocker compose exec -T -e PLUMBER_BOOTSTRAP_CLIENT_SECRET -e PLUMBER_BOOTSTRAP_TOKEN backend plumber-bootstrap \ -base-url https://gitlab.mydomain.com -client-id <application-id> -scope group -root-group <path>Step 8: First login
Open https://<your_plumber_domain> and sign in with GitLab as a Plumber Admin to finish the setup.
🧩 Plumber component in your GitLab
Pipelines include the Plumber CI/CD component from gitlab.com/getplumber/plumber. If your GitLab cannot reach gitlab.com, or you prefer a copy you control, the installer offers to copy it into a project of your instance and to publish its latest version to your CI/CD catalog. The same operation runs on its own at any time, from the installation repository:
read -rs -p "GitLab token: " PLUMBER_COMPONENT_TOKEN; echo; export PLUMBER_COMPONENT_TOKEN./scripts/component-mirror.sh --gitlab-url https://gitlab.mydomain.com --group my-groupIt creates (or reuses) the project my-group/plumber with the description the catalog requires, copies every branch and tag, flags the project as a CI/CD catalog project, runs the release pipeline on the latest tag and checks that the version is listed in the catalog before it reports success. It needs:
- a token with the
apiscope from an Owner of the group (or an instance Admin), read from the environment for this run only and never stored - a runner available to the new project that can pull
registry.gitlab.com/gitlab-org/release-cli
When it succeeds, set the copy as the component in Plumber, as an Admin, in Settings > Component: component_path is my-group/plumber, and component_ref stays empty to follow the latest version (or holds a tag to pin it). Pipelines then include gitlab.mydomain.com/my-group/plumber/plumber@<version>.
Info
The component’s job still pulls the getplumber/plumber image from Docker Hub. Mirroring that image into your registry is a separate step: set the image input of the component to your copy.
⏫ Update
To move to a new version, run the update script from the repository root:
./scripts/update.shThe backend migrates the database on boot, so the first start after an update can take longer than usual. Add --component to also refresh your copy of the Plumber component and publish its newest version (the script asks for the Owner token).
Caution
Upgrades are sequential and additive: go through the releases in order, do not skip a line of the history by jumping several versions at once. To roll back, check out the previous release tag (git checkout v2.0.0) and run docker compose up -d. Never downgrade the database by hand.
🔄 Backup and restore
A full backup of a Plumber system holds three things:
.env, the configuration file (it holds the encryption key)- the PostgreSQL database
- the custom CA certificates, if you use any
Two scripts in the installation repository cover both directions: scripts/backup.sh and scripts/restore.sh.
💽 Backup
From your installation repository, run the backup script, where 18 is the PostgreSQL version you run:
./scripts/backup.sh 18The archive lands in the backups directory as backup_plumber-<date>.tar.gz. Optional S3 arguments upload it:
./scripts/backup.sh 18 s3://bucket-name/folder https://s3-endpoint-url region access-key secret-keyDanger
The archive contains .env, and therefore PLUMBER_TOKEN_ENCRYPTION_KEY. The database dump is unreadable without that key, and the key cannot be recreated: store the archive as the secret it is, and never restore a dump with a different .env.
Info
A cron job keeps backups regular. This one runs every day at 2am:
0 2 * * * /plumber-platform/scripts/backup.sh 18Add it to your crontab with crontab -e (see this cron how-to).
🛳️ Restore
To restore a backup on a fresh system:
Check the new system against the requirements
Copy the backup file onto it
Clone the installation repository
Terminal window git clone https://github.com/getplumber/plumber-platform.git plumber-platformcd plumber-platformIf the server’s IP address changed, update your DNS records. See Configure your DNS
Start the stack
Terminal window docker compose up -dLaunch the restore script
Terminal window ./scripts/restore.sh 18 <path_to_your_backup_file>An S3 URL (
s3://bucket-name/folder/backup-file.tar.gz) works too, followed by the endpoint, region and credentials.
The restore puts back the .env of the backup. That is deliberate: the dumped data is sealed with the PLUMBER_TOKEN_ENCRYPTION_KEY of that file, and only that file can open it again.