# ============================================================================== # OpenProject — Docker Compose (stable/17) # With SMTP outbound-email support # ============================================================================== # Based on: # https://www.openproject.org/docs/installation-and-operations/installation/docker-compose/ # https://github.com/opf/openproject-docker-compose (branch: stable/17) # https://www.openproject.org/docs/installation-and-operations/configuration/outbound-emails/ # https://www.openproject.org/docs/installation-and-operations/configuration/environment/ # # Usage: # 1. cp .env.example .env # then edit .env with your real values # 2. sudo mkdir -p /var/openproject/assets # 3. sudo chown 1000:1000 -R /var/openproject/assets # 4. docker compose up -d --build --pull always # # SMTP variables live in the .env file (see .env.example). # They are injected into every OpenProject container via the x-op-app anchor. # ============================================================================== version: "3.7" # --------------------------------------------------------------------------- # Networks – frontend faces the proxy; backend is DB / cache only. # --------------------------------------------------------------------------- networks: frontend: backend: # --------------------------------------------------------------------------- # Volumes – persisted between restarts / upgrades. # pgdata – PostgreSQL WAL + tables # opdata – uploaded attachments & assets # --------------------------------------------------------------------------- volumes: pgdata: opdata: # =========================================================================== # YAML Anchors – shared restart policy, image tag, and environment block. # =========================================================================== x-op-restart-policy: &restart_policy restart: unless-stopped x-op-image: &image image: openproject/openproject:${TAG:-17-slim} # --------------------------------------------------------------------------- # x-op-app – merged into every OpenProject container. # All SMTP_* variables are pulled from the .env file so that secrets are # never hard-coded in this file. See .env.example for every placeholder. # --------------------------------------------------------------------------- x-op-app: &app <<: [*image, *restart_policy] environment: # --- Core OpenProject ------------------------------------------------ OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}" OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}" OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}" OPENPROJECT_EDITION: "${OPENPROJECT_EDITION:-standard}" # --- Database --------------------------------------------------------- DATABASE_URL: "${DATABASE_URL:-postgres://postgres:${POSTGRES_PASSWORD:-p4ssw0rd}@db/openproject?pool=20&encoding=unicode&reconnect=true}" # --- Cache ------------------------------------------------------------ OPENPROJECT_CACHE__MEMCACHE__SERVER: "cache:11211" OPENPROJECT_RAILS__CACHE__STORE: "memcache" # --- Threads ---------------------------------------------------------- RAILS_MIN_THREADS: "${RAILS_MIN_THREADS:-4}" RAILS_MAX_THREADS: "${RAILS_MAX_THREADS:-16}" # --- Collaborative editing (Hocuspocus) ------------------------------ OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL: "${COLLABORATIVE_SERVER_URL:-wss://${OPENPROJECT_HOST__NAME}/hocuspocus}" OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET: "${COLLABORATIVE_SERVER_SECRET:-OVERRIDE_ME_PLEASE}" # --- Inbound email (IMAP) – disabled by default ----------------------- IMAP_ENABLED: "${IMAP_ENABLED:-false}" # ================================================================ # SMTP – Outbound e-mail configuration # ================================================================ # Every variable below maps to an OpenProject environment variable # that is documented at: # /docs/installation-and-operations/configuration/outbound-emails/ # /docs/installation-and-operations/configuration/environment/ # # Setting these via environment variables **disables** the matching # form in Administration → Emails and notifications (by design). # # Common SMTP_PORT values: # 587 – submission with STARTTLS (most providers, recommended) # 465 – implicit SSL/TLS # 25 – unencrypted (never use in production) # # Common SMTP_AUTHENTICATION values: # plain – most cloud providers (Gmail, Outlook, SendGrid …) # login – some legacy / on-premises servers # cram_md5 # # For SendGrid specifically: # SMTP_USER_NAME=apikey # SMTP_PASSWORD= # ================================================================ EMAIL_DELIVERY_METHOD: "${EMAIL_DELIVERY_METHOD:-smtp}" SMTP_ADDRESS: "${SMTP_ADDRESS}" SMTP_PORT: "${SMTP_PORT:-587}" SMTP_DOMAIN: "${SMTP_DOMAIN}" SMTP_AUTHENTICATION: "${SMTP_AUTHENTICATION:-plain}" SMTP_USER_NAME: "${SMTP_USER_NAME}" SMTP_PASSWORD: "${SMTP_PASSWORD}" SMTP_ENABLE_STARTTLS_AUTO: "${SMTP_ENABLE_STARTTLS_AUTO:-true}" SMTP_SSL: "${SMTP_SSL:-false}" SMTP_TIMEOUT: "${SMTP_TIMEOUT:-5}" # Envelope sender – the "From" address that appears in every mail. # Must be a valid address on your SMTP account unless your provider # allows arbitrary senders. OPENPROJECT_MAILER__FROM__ADDRESS: "${MAILER_FROM_ADDRESS:-openproject@example.com}" volumes: - "${OPDATA:-opdata}:/var/openproject/assets" # =========================================================================== # Services # =========================================================================== services: # ----------------------------------------------------------------------- # db – PostgreSQL 16 # Stores all application data. The named volume pgdata persists the # data directory so it survives container re-creations. # ----------------------------------------------------------------------- db: <<: *restart_policy image: postgres:16-alpine networks: - backend volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-p4ssw0rd}" POSTGRES_DB: openproject POSTGRES_USER: postgres healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d openproject"] interval: 10s timeout: 5s retries: 5 # ----------------------------------------------------------------------- # cache – Memcached # Used by Rails for fragment / page caching. # ----------------------------------------------------------------------- cache: <<: *restart_policy image: memcached:alpine networks: - backend # ----------------------------------------------------------------------- # seeder – one-shot container # Runs database migrations and seeds the initial admin user. # Exits with code 0 after the first successful run; subsequent starts # are no-ops. # ----------------------------------------------------------------------- seeder: <<: *app networks: - backend command: ["seeds"] depends_on: db: condition: service_healthy cache: condition: service_started # ----------------------------------------------------------------------- # web – Puma application server (serves HTTP requests) # ----------------------------------------------------------------------- web: <<: *app networks: - frontend - backend command: ["web"] depends_on: db: condition: service_healthy cache: condition: service_started seeder: condition: service_completed_successfully healthcheck: test: - "CMD" - "curl" - "-f" - "http://localhost:8080${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}/health_checks/default" interval: 30s timeout: 5s retries: 5 start_period: 60s expose: - "8080" # ----------------------------------------------------------------------- # worker – Active Job background processor # Handles asynchronous tasks such as sending notification mails, # exporting, repository indexing, etc. # # DNS block below resolves the SMTP issue documented in OP#44515: # "SMTP setup fails: Network is unreachable." # If your corporate DNS is sufficient, replace 8.8.8.8 with your # internal resolver. # ----------------------------------------------------------------------- worker: <<: *app networks: - frontend # needs outbound access for SMTP - backend command: ["worker"] depends_on: db: condition: service_healthy cache: condition: service_started seeder: condition: service_completed_successfully # Explicit DNS – prevents "Network is unreachable" when the container # tries to connect to an external SMTP server. See OP#44515. dns: - "8.8.8.8" # ----------------------------------------------------------------------- # proxy – Caddy reverse proxy # Terminates TLS (if a certificate is available) and forwards to web. # Exposes the single public port defined by $PORT (default 8080). # # NOTE: In production, it is strongly recommended to place OpenProject # behind your own TLS-terminating reverse proxy (Nginx, Traefik, …) # and configure Caddy's trusted_proxies accordingly. # ----------------------------------------------------------------------- proxy: <<: *restart_policy image: openproject/openproject:${TAG:-17-slim} networks: - frontend ports: - "${PORT:-8080}:80" volumes: - "${OPDATA:-opdata}:/var/openproject/assets" command: ["proxy"] depends_on: web: condition: service_healthy environment: OPENPROJECT_HTTPS: "${OPENPROJECT_HTTPS:-true}" OPENPROJECT_HOST__NAME: "${OPENPROJECT_HOST__NAME:-localhost:8080}" OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "${OPENPROJECT_RAILS__RELATIVE__URL__ROOT:-}" # ----------------------------------------------------------------------- # hocuspocus – WebSocket collaboration server # Enables real-time co-editing of documents. # ----------------------------------------------------------------------- hocuspocus: <<: *restart_policy image: openproject/openproject:${TAG:-17-slim} networks: - frontend - backend command: ["hocuspocus"] depends_on: web: condition: service_healthy environment: OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET: "${COLLABORATIVE_SERVER_SECRET:-OVERRIDE_ME_PLEASE}" expose: - "3000"