Custom OpenProject deployment configuration with Docker Compose.
- Clone this repository
- Copy
.env.prod.example
to.env.prod
and customize - Run:
docker-compose -f docker-compose.simple.yml up -d
docker-compose.simple.yml
- Main Docker Compose configurationdocker-compose.prod.yml
- Production overrides.env.prod.example
- Environment templatebackup.sh
- Backup scripthealth-check.sh
- Health monitoringPRODUCTION_DEPLOYMENT.md
- Deployment guide
This guide documents how to deploy OpenProject behind a Cloudflare Tunnel, exposing it to a custom domain (e.g., app.irm-project.uk
).
- Cloudflare account with a registered domain (e.g.,
irm-project.uk
) - Cloudflared installed on the host
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared chmod +x /usr/local/bin/cloudflared
- Docker + Docker Compose (v2) installed
-
Authenticate cloudflared:
cloudflared tunnel login
-
Create a named tunnel:
cloudflared tunnel create illinirm_proj_management
-
Route your domain to the tunnel:
cloudflared tunnel route dns illinirm_proj_management app.irm-project.uk
Create /etc/cloudflared/config.yml
:
tunnel: illinirm_proj_management
credentials-file: /etc/cloudflared/<TUNNEL_ID>.json
protocol: http2
ingress:
- hostname: app.irm-project.uk
service: http://localhost:3000
- service: http_status:404
Install as a systemd service:
sudo cloudflared service install
sudo systemctl enable --now cloudflared
Check status:
cloudflared tunnel info illinirm_proj_management
# Should show >=1 active CONNECTIONS
Your docker-compose.yml
should define services: db
, cache
, proxy
, web
, worker
, cron
, seeder
.
TAG=16-slim
PORT=3000
# Domain + HTTPS
OPENPROJECT_HOST__NAME=app.irm-project.uk
OPENPROJECT_HTTPS=true
OPENPROJECT_HSTS=true
POSTGRES_PASSWORD=p4ssw0rd
services:
proxy:
ports:
- "${PORT}:80"
web:
image: openproject/openproject:${TAG}
environment:
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS}"
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME}"
OPENPROJECT_HSTS: "${OPENPROJECT_HSTS}"
# ... other config unchanged
worker:
image: openproject/openproject:${TAG}
environment:
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS}"
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME}"
OPENPROJECT_HSTS: "${OPENPROJECT_HSTS}"
cron:
image: openproject/openproject:${TAG}
environment:
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS}"
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME}"
OPENPROJECT_HSTS: "${OPENPROJECT_HSTS}"
seeder:
image: openproject/openproject:${TAG}
environment:
OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS}"
OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME}"
OPENPROJECT_HSTS: "${OPENPROJECT_HSTS}"
Bring up the stack:
docker compose up -d --build
Check container status:
docker compose ps
Check environment variables inside container:
docker exec -it $(docker compose ps -q web) env | grep OPENPROJECT
Verify local backend:
curl -I -H 'Host: app.irm-project.uk' http://127.0.0.1:3000
# Expected 200/302
Verify public access (through Cloudflare Tunnel):
curl -I https://app.irm-project.uk
# Expected 200/302
-
400 Bad Request (Via: Caddy) → Ensure
OPENPROJECT_HOST__NAME
is set to your domain (app.irm-project.uk
). → Rebuild all containers (web
,worker
,cron
,seeder
). -
Tunnel not active → Check with
cloudflared tunnel info illinirm_proj_management
. → Restart service:sudo systemctl restart cloudflared
-
DNS not resolving → Ensure
app.irm-project.uk
has a CNAME pointing to your tunnel in Cloudflare DNS.
Your OpenProject instance should now be accessible at:
Behind Cloudflare Tunnel, with HTTPS automatically handled.