Skip to Content

Production Guide: Deploy Docmost with Docker Compose and HAProxy on Ubuntu

A production-oriented Docmost setup with TLS, secrets hygiene, health checks, backups, and operational troubleshooting.

Documentation debt quietly slows delivery. Teams lose architecture context, runbooks drift, and onboarding repeatedly interrupts senior engineers. A shared docs platform helps, but many teams postpone deployment because they worry about creating another fragile service.

This guide provides a production-oriented path for running Docmost with Docker Compose and a containerized HAProxy edge. The emphasis is reliability and repeatability: secure defaults, explicit health checks, resilient restart behavior, backup routines, and practical day-two troubleshooting.

You will deploy Docmost with PostgreSQL and Redis on a private Docker network, expose only TLS entrypoints, validate service health end to end, and leave with an operational checklist your team can reuse during upgrades.

Architecture and flow overview

Traffic enters HAProxy on ports 80 and 443. HTTP is redirected to HTTPS, and encrypted requests are forwarded to Docmost on the internal Docker network. PostgreSQL stores persistent data while Redis supports cache and job coordination.

This separation keeps the stack maintainable: you can rotate certificates and proxy rules without touching application containers, and you can tune data services independently from web routing. Private service-to-service networking also reduces accidental exposure.

For operations, we include health checks, restart policies, version pinning guidance, secret boundaries, and backup mechanics that capture both database state and uploaded content.

Prerequisites

  • Ubuntu 22.04/24.04 with sudo access
  • Domain pointed to your server, e.g. docs.example.com
  • Docker Engine + Compose plugin
  • Ports 80 and 443 reachable
  • TLS certificate + private key bundle
  • Basic shell familiarity

Use a dedicated service account where possible and do not commit secrets to Git.

Step-by-step deployment

1) Prepare folders

Create stable directories for compose files, proxy config, certs, scripts, and backups.

mkdir -p /opt/docmost/{compose,haproxy/certs,haproxy/config,scripts,backups}
cd /opt/docmost/compose

If copy does not work, manually select and copy.

2) Create private Docker network

Use an explicit shared network for stack isolation.

docker network create docmost_net || true

If copy does not work, manually select and copy.

3) Write environment variables

Store sensitive values in a local file with strict permissions.

cat > .env <<'EOF'
DOCMOST_DOMAIN=docs.example.com
POSTGRES_DB=docmost
POSTGRES_USER=docmost
POSTGRES_PASSWORD=replace-with-strong-db-password
REDIS_PASSWORD=replace-with-strong-redis-password
APP_SECRET=replace-with-long-random-app-secret
EOF
chmod 600 .env

If copy does not work, manually select and copy.

4) Define application services

Compose declares Docmost, PostgreSQL, and Redis with health checks and persistent volumes.

cat > docker-compose.yml <<'EOF'
services:
  postgres:
    image: postgres:16
    container_name: docmost_postgres
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - docmost_pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    networks: [docmost_net]

  redis:
    image: redis:7-alpine
    container_name: docmost_redis
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "PING"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    networks: [docmost_net]

  docmost:
    image: docmost/docmost:latest
    container_name: docmost_app
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      APP_URL: https://${DOCMOST_DOMAIN}
      APP_SECRET: ${APP_SECRET}
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
    volumes:
      - docmost_storage:/app/data/storage
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/api/health"]
      interval: 15s
      timeout: 5s
      retries: 15
    networks: [docmost_net]

volumes:
  docmost_pgdata:
  docmost_storage:

networks:
  docmost_net:
    external: true
EOF

If copy does not work, manually select and copy.

5) Configure containerized HAProxy

Keep proxy files under your project path and run HAProxy as a service container.

cat > /opt/docmost/haproxy/config/haproxy.cfg <<'EOF'
global
  daemon
  maxconn 4096

defaults
  mode http
  option httplog
  timeout connect 5s
  timeout client 60s
  timeout server 60s

frontend http_in
  bind *:80
  redirect scheme https code 301 if !{ ssl_fc }

frontend https_in
  bind *:443 ssl crt /certs/docs.example.com.pem alpn h2,http/1.1
  http-request set-header X-Forwarded-Proto https
  http-request set-header X-Forwarded-For %[src]
  default_backend app

backend app
  option httpchk GET /api/health
  http-check expect status 200
  server docmost docmost_app:3000 check
EOF

If copy does not work, manually select and copy.

cat > /opt/docmost/haproxy/docker-compose.proxy.yml <<'EOF'
services:
  haproxy:
    image: haproxy:2.9
    container_name: docmost_haproxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /opt/docmost/haproxy/config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
      - /opt/docmost/haproxy/certs:/certs:ro
    restart: unless-stopped
    networks: [docmost_net]
networks:
  docmost_net:
    external: true
EOF

If copy does not work, manually select and copy.

6) Start application and proxy

cd /opt/docmost/compose
docker compose --env-file .env up -d
docker compose -f /opt/docmost/haproxy/docker-compose.proxy.yml up -d
docker ps --format "table {{.Names}}\t{{.Status}}"

If copy does not work, manually select and copy.

7) Baseline hardening

Restrict secret files and verify runtime logs before handing the service to users.

chmod 600 /opt/docmost/compose/.env
docker logs --tail=120 docmost_app
docker logs --tail=120 docmost_haproxy

If copy does not work, manually select and copy.

Configuration and secret-handling best practices

Rotate secrets on a schedule and after access changes. Keep deployment code versioned, but keep runtime secrets in a protected local file or an external secret manager. If you later adopt Vault, SOPS, or Doppler, preserve variable names so migration is low-risk.

Certificates should be renewed automatically and validated before reload. Treat proxy config checks as a release gate and keep one known-good certificate bundle for emergency rollback.

Use explicit image tags for predictable upgrades, then promote versions from staging to production only after smoke tests pass.

Backup and recovery workflow

Back up both PostgreSQL data and Docmost storage. A green backup job is not enough unless restore testing also succeeds.

cat > /opt/docmost/scripts/backup_docmost.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date +%F_%H%M%S)
OUT=/opt/docmost/backups/$TS
mkdir -p "$OUT"
cd /opt/docmost/compose
source .env
docker exec docmost_postgres pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > "$OUT/postgres.sql"
tar -czf "$OUT/docmost_storage.tar.gz" -C /var/lib/docker/volumes/docmost_storage/_data .
sha256sum "$OUT/postgres.sql" "$OUT/docmost_storage.tar.gz" > "$OUT/checksums.txt"
EOF
chmod +x /opt/docmost/scripts/backup_docmost.sh

If copy does not work, manually select and copy.

(crontab -l 2>/dev/null; echo "20 2 * * * /opt/docmost/scripts/backup_docmost.sh >> /opt/docmost/backups/backup.log 2>&1") | crontab -

If copy does not work, manually select and copy.

Verification checklist

  • DNS resolves correctly
  • HTTPS endpoint responds
  • HAProxy health checks pass
  • Docmost login and page creation work
  • Restart test keeps data intact
curl -I https://docs.example.com
docker ps --filter name=docmost --format "{{.Names}} {{.Status}}"
docker compose -f /opt/docmost/compose/docker-compose.yml restart docmost
docker compose -f /opt/docmost/compose/docker-compose.yml ps

If copy does not work, manually select and copy.

Common issues and fixes

HTTPS certificate errors

Check that your PEM bundle contains full chain plus private key and that the file is mounted at the path referenced by HAProxy. Restart only after validating syntax.

502/503 from the proxy

Confirm Docmost is healthy on the internal network and that backend naming matches service/container naming in your compose setup.

Database connection failures

Most incidents come from mismatched credentials between initialized database state and current environment variables. Reconcile values and test readiness from the PostgreSQL container.

Slow UI during peak edits

Inspect CPU saturation, disk IOPS, and PostgreSQL contention. As usage grows, move PostgreSQL to dedicated infrastructure and keep app containers stateless.

FAQ

Can I run Docmost without publishing port 3000?

Yes. Keep the app private and expose only HAProxy on 80/443.

Should I use secrets tooling instead of .env?

Yes when available. The .env approach is a practical baseline, not the end state for mature teams.

How often should backups run?

At least daily; high-change teams may need hourly DB dumps and more frequent storage snapshots.

What is the safest upgrade path?

Back up, test in staging, then roll out during a maintenance window with a rollback image ready.

Can I swap HAProxy for another proxy later?

Yes. This architecture keeps proxy and app concerns separate, so migration is straightforward.

When should PostgreSQL move off-box?

When uptime requirements, write load, or compliance needs exceed single-host risk tolerance.

Related internal guides

Talk to us

If you want help shipping a secure, production-ready Docmost deployment with observability, backup validation, and upgrade planning, our team can help.

Contact Us

Production Guide: Deploy GlitchTip with Docker Compose + Caddy + PostgreSQL on Ubuntu
A production-ready blueprint for self-hosted error monitoring with TLS, persistence, backups, and operational validation.