Skip to content

How to Deploy OpenProject on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-host OpenProject Community Edition on a VPS with the official Docker Compose setup — generated secrets, the port kept off the public internet, HTTPS through Caddy, and backups.

Before you start
  • A VPS with at least 4 GB RAM
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain or subdomain you can point at the server
  • Docker Engine + Compose installed (see the base guide below)
  • git
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What OpenProject is

OpenProject is open-source software for project, product and portfolio management. The Community Edition covers work packages (tasks, features, milestones and their hierarchy), Gantt charts, agile and kanban boards, Scrum backlogs, time tracking and a project wiki, with TOTP and WebAuthn two-factor authentication included. It is written in Ruby and TypeScript on PostgreSQL, licensed GPL-3.0, and rated 4 / 5 to deploy — the highest of the project-management tools in the catalog, mostly because of its size and its HTTPS assumptions.

LDAP group sync, SSO, resource management and custom branding are Enterprise add-ons and are not part of the free build.

Comparing options? Plane vs OpenProject sets it against the newer, lighter-looking alternative; Deploy Plane on a VPS covers that one.

Server sizing

The Compose setup runs OpenProject's web server, a background worker, a cron container, a one-off seeder that runs migrations, PostgreSQL, memcached, a small proxy, a collaborative-editing server and an autoheal watchdog.

  • Minimum: 4 GB RAM — the catalog's floor for OpenProject.
  • Measured: on a GCP e2-standard-2 running Ubuntu 26.04 with Docker 29.8.1, the idle stack used ~569 MB of RAM and ~2.7 GB of disk for images and data (September 2026).

The idle figure is low because nobody is using it; Rails workers grow with concurrent users, and the first start (asset setup and migrations) is the heaviest moment. Stay at 4 GB or above, with a 40 GB+ disk for attachments.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root deploy user and a ufw firewall. If not, work through Docker & Compose on Ubuntu first.

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
sudo apt-get install -y git
Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install OpenProject (Docker Compose)

Clone upstream's openproject-docker-compose repository on the stable/17 branch — the release line the catalog verified — and start from its example environment file:

cd ~
[ -d openproject ] || git clone https://github.com/opf/openproject-docker-compose.git --depth=1 --branch=stable/17 openproject
cd ~/openproject
[ -f .env ] || cp .env.example .env

The example .env sets TAG=17-slim (the image upstream recommends for this setup) and OPDATA=/var/openproject/assets, a host directory for attachments that upstream says to create and hand to UID 1000:

sudo mkdir -p /var/openproject/assets
sudo chown 1000:1000 -R /var/openproject/assets

Configure .env

Four changes, all in .env so they survive git pull:

  • SECRET_KEY_BASE ships as OVERWRITE_ME. It derives the Rails keys; changing it later invalidates every session and remembered 2FA device.
  • COLLABORATIVE_SERVER_SECRET ships as secret12345, and upstream's README tells you to override it.
  • The database password is p4ssw0rd in both DATABASE_URL and the compose file's POSTGRES_PASSWORD default. Set both to the same new value before the first start — Postgres only reads it on initialisation.
  • The host name and HTTPS: OPENPROJECT_HOST__NAME is your domain, and OPENPROJECT_HTTPS goes back to true, because this install sits behind Caddy. COLLABORATIVE_SERVER_URL becomes wss:// on that domain.
cd ~/openproject
if grep -q '^SECRET_KEY_BASE=OVERWRITE_ME' .env; then
  DB_PW=$(openssl rand -hex 16)
  sed -i \
    -e "s|^SECRET_KEY_BASE=.*|SECRET_KEY_BASE=$(openssl rand -hex 64)|" \
    -e "s|^COLLABORATIVE_SERVER_SECRET=.*|COLLABORATIVE_SERVER_SECRET=$(openssl rand -hex 32)|" \
    -e "s|postgres:p4ssw0rd@db|postgres:${DB_PW}@db|" \
    .env
  echo "POSTGRES_PASSWORD=${DB_PW}" >> .env
fi
sed -i \
  -e 's|^OPENPROJECT_HTTPS=.*|OPENPROJECT_HTTPS=true|' \
  -e 's|^OPENPROJECT_HOST__NAME=.*|OPENPROJECT_HOST__NAME=op.example.com|' \
  -e 's|^COLLABORATIVE_SERVER_URL=.*|COLLABORATIVE_SERVER_URL=wss://op.example.com/hocuspocus|' \
  -e 's|^PORT=.*|PORT=127.0.0.1:8080|' \
  .env
chmod 600 .env
grep -E '^(TAG|OPENPROJECT_HTTPS|OPENPROJECT_HOST__NAME|PORT|COLLABORATIVE_SERVER_URL)=' .env

Replace op.example.com with your domain. PORT=127.0.0.1:8080 keeps the stack's proxy on the loopback interface; upstream's README points out that without the address it binds to 0.0.0.0 and is public.

Start it

cd ~/openproject
docker compose up -d --build --pull always
timeout 900 bash -c 'until curl -fs -H "Host: op.example.com" -H "X-Forwarded-Proto: https" http://127.0.0.1:8080/health_checks/default >/dev/null; do sleep 10; done'
docker compose ps --format '{{.Service}}\t{{.Status}}'

--build builds the small proxy image locally, which is expected: upstream's troubleshooting notes that a "pull access denied for openproject/proxy" message during this step is a warning, not a failure. The first start runs the seeder, which creates the database schema, so the health check can take several minutes to answer. The two headers in the check stand in for Caddy: with OPENPROJECT_HTTPS=true, OpenProject expects requests for its own host name arriving over HTTPS.

HTTPS + domain

Point an A record for op.example.com at the server's public IP, wait for it to resolve, then terminate TLS with Caddy:

op.example.com {
    reverse_proxy 127.0.0.1:8080
}

Caddy sets X-Forwarded-Proto and X-Forwarded-Host, and the stack's own proxy passes them on to OpenProject, so no extra headers are needed. It also proxies the WebSocket connection that collaborative editing uses at /hocuspocus.

Without a TLS proxy and with OPENPROJECT_HTTPS=true, browsers end up at an https:// address nothing is answering — upstream's README describes that as an ERR_SSL_PROTOCOL_ERROR. OPENPROJECT_HTTPS=false exists only for a first look without a domain.

First login

Open https://op.example.com and sign in with upstream's default credentials, admin / admin. OpenProject makes you set a new password on that first login; do it before anything else, since the default is public knowledge.

Then, under Administration:

  • Authentication: decide whether self-registration is allowed at all, and turn on two-factor authentication (TOTP and WebAuthn are in the free edition).
  • Email: configure SMTP so notifications and invitations are delivered.
  • Users: create accounts or invite your team instead of sharing admin.

Backups

Two things matter: the PostgreSQL database and the attachments in /var/openproject/assets.

cd ~/openproject
mkdir -p ~/openproject-backups
docker compose exec -T db pg_dump -U postgres openproject | gzip > ~/openproject-backups/openproject-db-$(date +%F).sql.gz
sudo tar czf ~/openproject-backups/openproject-assets-$(date +%F).tar.gz -C /var/openproject assets
ls -lh ~/openproject-backups

pg_dump gives a consistent snapshot without stopping the stack. Copy the backups and .env off the server; without SECRET_KEY_BASE a restore works but logs everyone out and drops remembered 2FA devices.

Upgrades

Minor releases within a line come from pulling new images:

cd ~/openproject
docker compose pull --ignore-buildable
docker compose up -d --build
docker compose ps --format '{{.Service}}\t{{.Status}}'

The seeder runs any migrations on start. Major upgrades (17 to 18, say) mean switching the repository to the new stable/<n> branch and changing TAG — back up first and read OpenProject's upgrade notes for that version. The .env example also warns that POSTGRES_VERSION must match the version your data directory was created with; upgrading Postgres is a separate dump-and-restore job.

Troubleshooting

ERR_SSL_PROTOCOL_ERROR or a redirect loop. HTTPS mode and the proxy disagree. Behind Caddy, keep OPENPROJECT_HTTPS=true and make sure you are visiting the https:// URL; without a proxy, set it to false.

Links and redirects point at the wrong address. OPENPROJECT_HOST__NAME doesn't match the domain in your browser. Fix it in .env and run docker compose up -d.

Attachments fail to save. /var/openproject/assets is not owned by UID 1000. Re-run the chown from the install step.

The web container keeps restarting. Read docker compose logs web seeder. The autoheal container restarts web when its health check fails, so a database connection error — often a password changed after the first start — shows up as a restart loop.

Verification + next steps

You're done when you can: load https://op.example.com over a valid certificate, log in and replace the default admin password, create a project with work packages and see them on a Gantt chart and a board, attach a file, and find last night's dump off the server.

From there: set up SMTP, turn on 2FA for every account, and put docker-compose.override.yml to use for any change to the compose file itself — upstream recommends that over editing docker-compose.yml, which git pull overwrites. For hosting options, see Best VPS for Docker.

Next steps

How to self-host OpenProject →More self-hosted project management tools →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.