HedgeDoc Collaborative Editor: Host Your Own Real-Time Markdown Pad
Deploy HedgeDoc using Docker Compose. Author, share, and collaborate on markdown documents in real-time on your own VPS.
Directory Structure and Preparation
Before deploying the containers, establish a structured directory layout under /opt/hedgedoc to persist database records and file uploads. Run the following commands to create the directories and set correct ownership:
sudo mkdir -p /opt/hedgedoc/volumes/db_data
sudo mkdir -p /opt/hedgedoc/volumes/uploads
sudo chown -R 10000:10000 /opt/hedgedoc/volumes/uploads
Note: The HedgeDoc container runs as UID/GID 10000 by default. Assigning ownership of the uploads directory to 10000 prevents permission errors when uploading images.
Docker Compose Configuration
Create /opt/hedgedoc/docker-compose.yml with the configuration below. This deployment pairs HedgeDoc with PostgreSQL 15 on an internal bridge network.
version: '3.8'
services:
db:
image: postgres:15-alpine
container_name: hedgedoc_db
restart: always
environment:
POSTGRES_USER: hedgedoc
POSTGRES_PASSWORD: MustJwDRIBziphPdGoOpWao2GDZL
POSTGRES_DB: hedgedoc
volumes:
- /opt/hedgedoc/volumes/db_data:/var/lib/postgresql/data
networks:
- hedgedoc_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U hedgedoc"]
interval: 5s
timeout: 5s
retries: 5
hedgedoc:
image: quay.io/hedgedoc/hedgedoc:1.9.9
container_name: hedgedoc_app
restart: always
depends_on:
db:
condition: service_healthy
environment:
- NODE_ENV=production
- CMD_DB_URL=postgres://hedgedoc:MustJwDRIBziphPdGoOpWao2GDZL@db:5432/hedgedoc
- CMD_DOMAIN=hedgedoc.example.com
- CMD_PROTOCOL_USESSL=true
- CMD_URL_ADDPORT=false
- CMD_SESSION_SECRET=a_very_long_random_string_for_session_encryption_key
- CMD_IMAGE_UPLOAD_TYPE=filesystem
- CMD_ALLOW_FREEURL=true
- CMD_DEFAULT_PERMISSION=editable
volumes:
- /opt/hedgedoc/volumes/uploads:/hedgedoc/public/uploads
ports:
- "127.0.0.1:3000:3000"
networks:
- hedgedoc_net
networks:
hedgedoc_net:
driver: bridge
Essential Parameters Explained
CMD_DB_URL: Specifies the PostgreSQL connection string. The hostnamedbmatches the container service name inside the shared bridge network.CMD_DOMAIN: Set this to your fully qualified domain name (FQDN). It ensures absolute URLs generated in emails and invites match your public address.CMD_PROTOCOL_USESSL: Instructs HedgeDoc that it is served behind an SSL-terminating reverse proxy. Cookies will be set with theSecureflag.CMD_SESSION_SECRET: A cryptographically secure random string used to sign session cookies.CMD_IMAGE_UPLOAD_TYPE: Configured tofilesystemto store images locally. They will persist in the/opt/hedgedoc/volumes/uploadsdirectory.
Nginx Reverse Proxy Setup
HedgeDoc relies on WebSockets for real-time collaborative editing. The reverse proxy must be configured to pass the standard connection upgrade headers, or client connections will degrade to HTTP polling.
Virtual Host Configuration
Place the following server block configuration in your Nginx configuration directory (e.g., /etc/nginx/sites-available/hedgedoc.conf):
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name hedgedoc.example.com;
# Redirect all HTTP traffic to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name hedgedoc.example.com;
# SSL Certificates (managed via Certbot / Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/hedgedoc.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hedgedoc.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Increase client body size to allow image uploads
client_max_body_size 10M;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400;
}
}
Applying the Nginx Configuration
Validate the syntax of your configuration and reload Nginx:
sudo ln -s /etc/nginx/sites-available/hedgedoc.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Deployment and Verification
Start the containers in detached mode:
cd /opt/hedgedoc
docker compose up -d
Confirm that the application is running and the database connection is initialized by viewing the container logs:
docker compose logs -f hedgedoc
You should see output similar to:
2026-07-06T21:30:00.000Z - info: DB connection has been established successfully.
2026-07-06T21:30:01.000Z - info: HTTP Server listening at http://0.0.0.0:3000
Backup and Maintenance
Backing up the Database
To perform a hot backup of the PostgreSQL database without interrupting the service, execute:
docker exec -t hedgedoc_db pg_dump -U hedgedoc hedgedoc > /opt/hedgedoc/backups/backup_$(date +%F).sql
Upgrading HedgeDoc
To upgrade the application container when a new version is released:
cd /opt/hedgedoc
docker compose pull hedgedoc
docker compose up -d --remove-orphans
The database migrations will run automatically upon container startup.