Teams that handle contracts, NDAs, onboarding documents, and compliance approvals quickly discover that ad-hoc e-signature workflows become operational risk. Documents get sent from personal accounts, evidence trails are scattered, and there is no clear control over retention or access revocation. A production deployment of DocuSeal gives IT and security teams centralized control over signature workflows while keeping the service close to existing identity, networking, and backup standards.
This guide shows a production-first way to deploy DocuSeal on Ubuntu using Docker Compose, Traefik, and PostgreSQL. The goal is not a demo stack; it is an operable service with explicit secret handling, durable data, rollout checks, and failure recovery practices that an infrastructure team can run with confidence.
You will implement TLS termination at the reverse proxy layer, isolate application networking, persist all critical state, and set up pragmatic verification commands that can be used during maintenance windows. If your team already runs Traefik for ingress and wants a clean path to add a secure self-hosted signing platform, this workflow fits naturally into that operating model.
Architecture and flow overview
The deployment uses three application layers and one operations layer. Traefik handles public HTTPS entry, certificate management, and request routing. DocuSeal serves application logic and workflow APIs. PostgreSQL stores core application state, including signing sessions and audit-relevant metadata. Docker volumes preserve state across host restarts and image updates. Operational controls are implemented via health checks, backup jobs, and explicit rollback points.
- Edge: Traefik entrypoints for HTTP/HTTPS with automatic redirection and TLS.
- Application: DocuSeal container on a private Docker network.
- Data: PostgreSQL container with persistent volume and constrained credentials.
- Operations: Backup script, log inspection routines, and post-deploy smoke tests.
Traffic flow: client request → Traefik router rule for the DocuSeal hostname → DocuSeal service → PostgreSQL for state operations. This separation allows you to rotate certificates, update DocuSeal, and tune database settings without changing public URLs.
Prerequisites
- Ubuntu 22.04+ host with at least 2 vCPU, 4 GB RAM, and 40+ GB SSD.
- DNS A record for your DocuSeal domain (for example,
sign.example.com). - Docker Engine + Docker Compose plugin installed.
- Traefik already running (or deployed in this same host stack).
- Firewall allowing 80/443 to Traefik and SSH access from trusted admin ranges.
- A secure secret-management process for generating and storing strong passwords.
Step-by-step deployment
Create an isolated project directory and separate secrets/config artifacts from compose definitions. Keep this repository private and avoid committing plaintext secrets.
sudo mkdir -p /opt/docuseal/{data,postgres,backups,config}
sudo chown -R $USER:$USER /opt/docuseal
cd /opt/docusealManual copy fallback: select the code block and copy it if the button is unavailable.
Create a dedicated Docker network so services communicate internally by name and remain hidden from the public interface.
docker network create docuseal_net || trueManual copy fallback: select the code block and copy it if the button is unavailable.
Prepare environment values. Use long random secrets, and set explicit domain and email values used by Traefik certificate provisioning.
cat > /opt/docuseal/config/.env << 'EOF'
DOMAIN=sign.example.com
[email protected]
POSTGRES_DB=docuseal
POSTGRES_USER=docuseal
POSTGRES_PASSWORD=CHANGE_ME_STRONG_DB_PASSWORD
DOCUSEAL_SECRET_KEY=CHANGE_ME_LONG_RANDOM_APP_SECRET
DOCUSEAL_URL=https://sign.example.com
EOF
chmod 600 /opt/docuseal/config/.envManual copy fallback: select the code block and copy it if the button is unavailable.
Deploy compose services. This example assumes an external Traefik instance reading Docker labels.
cat > /opt/docuseal/docker-compose.yml << 'EOF'
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
env_file: /opt/docuseal/config/.env
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- /opt/docuseal/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- docuseal_net
docuseal:
image: docuseal/docuseal:latest
restart: unless-stopped
env_file: /opt/docuseal/config/.env
depends_on:
postgres:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
- SECRET_KEY_BASE=${DOCUSEAL_SECRET_KEY}
- FORCE_SSL=true
- APP_URL=${DOCUSEAL_URL}
volumes:
- /opt/docuseal/data:/data
labels:
- traefik.enable=true
- traefik.http.routers.docuseal.rule=Host(`${DOMAIN}`)
- traefik.http.routers.docuseal.entrypoints=websecure
- traefik.http.routers.docuseal.tls=true
- traefik.http.routers.docuseal.tls.certresolver=letsencrypt
- traefik.http.services.docuseal.loadbalancer.server.port=3000
networks:
- docuseal_net
networks:
docuseal_net:
external: true
EOF
cd /opt/docuseal && docker compose up -dManual copy fallback: select the code block and copy it if the button is unavailable.
Once containers are healthy, lock down host permissions for config files and ensure backup directories are writable only by privileged operators.
sudo chmod 700 /opt/docuseal/config
sudo chmod 700 /opt/docuseal/backups
sudo chown -R root:root /opt/docuseal/configManual copy fallback: select the code block and copy it if the button is unavailable.
Configuration and secrets handling best practices
Use separate credentials for the database and any application admin account. Do not reuse host or monitoring credentials. If your team uses a central secrets manager, place only references on disk and inject runtime values during deployment. For smaller teams, encrypted-at-rest files with strict ownership and documented rotation windows are acceptable.
Minimum operational policies worth enforcing from day one:
- Rotate database credentials at least quarterly or immediately after operator turnover.
- Store a redacted copy of the compose file in documentation and keep full secrets outside shared docs.
- Restrict shell access to deployment hosts and require audited privilege escalation for edits under
/opt/docuseal. - Enable central logging and avoid logging raw documents or token values in application traces.
If you support multiple business units, segment each tenant deployment by hostname and data path. That gives you cleaner retention policies and incident boundaries when one team needs emergency maintenance without impacting others.
Verification checklist
After deployment, run a deterministic health sequence and save outputs in your change ticket. Verification is what separates a successful rollout from an unproven one.
cd /opt/docuseal
docker compose ps
docker compose logs --tail=80 docuseal
docker compose logs --tail=80 postgresManual copy fallback: select the code block and copy it if the button is unavailable.
Confirm TLS and HTTP routing from outside the host:
curl -I https://sign.example.com
curl -sS https://sign.example.com/ | head -n 5Manual copy fallback: select the code block and copy it if the button is unavailable.
Validate database responsiveness and backup viability before handing over to users:
docker exec -i $(docker compose ps -q postgres) pg_dump -U docuseal docuseal > /opt/docuseal/backups/docuseal_$(date +%F).sql
ls -lh /opt/docuseal/backups/Manual copy fallback: select the code block and copy it if the button is unavailable.
Run a user-level smoke test: create a test signer workflow, send a document to a controlled mailbox, complete the signature flow, and verify that status updates appear correctly in the dashboard. This should be repeated after every major version update.
Common issues and fixes
1) Certificate is not issued or browser shows insecure warning
Usually this is DNS mismatch, blocked port 80/443, or Traefik resolver misconfiguration. Verify the domain points to the right host, then inspect Traefik logs for ACME errors. Ensure only one router claims the hostname.
2) DocuSeal container restarts repeatedly
Check environment variable names first, then database connectivity. A malformed DATABASE_URL or wrong PostgreSQL password causes early process exits. Confirm postgres health check succeeds before app startup.
3) Slow page loads during peak usage
Review host memory pressure and database I/O latency. Increase postgres shared buffers conservatively, move to faster storage, and verify no noisy neighbor workload is competing for IOPS.
4) Document uploads fail intermittently
Inspect reverse proxy body-size limits, file storage permissions under /opt/docuseal/data, and any upstream security appliance enforcing strict request constraints.
5) Backup file exists but restore fails
Backups are only useful if restore is tested. Validate your restore process on a staging host monthly. Track required PostgreSQL version compatibility and keep restore scripts versioned.
Operationally, treat every fix as runbook input. Update your incident notes with exact commands, expected outputs, and a rollback path so on-call engineers can execute reliably under pressure.
FAQ
Can I run DocuSeal with SQLite instead of PostgreSQL in production?
For production, PostgreSQL is the safer default due to better concurrency handling, backup tooling, and observability. SQLite can be acceptable for test or very small single-user setups, but it limits scalability and operational flexibility.
Do I need a separate Traefik instance for DocuSeal?
No. Most teams reuse an existing Traefik edge layer and add a dedicated router rule for the DocuSeal hostname. Ensure label rules are explicit so there is no conflict with other services.
How often should I rotate DocuSeal and database secrets?
Quarterly is a strong baseline, with immediate rotation after staffing changes or suspected exposure. Automate rotation windows and verify restart procedures so secret updates do not cause prolonged downtime.
What is the safest way to apply updates?
Pin image tags, stage changes in non-production first, run smoke tests, then upgrade with a rollback point. Keep previous compose file and backup snapshots so you can reverse quickly if regressions appear.
How can I integrate this with SSO later?
Start with local auth if needed, but keep domain and reverse-proxy patterns compatible with your identity provider roadmap. Document callback URLs and trust boundaries early so SSO onboarding is straightforward later.
What should I monitor from day one?
At minimum: container restart count, request latency, TLS certificate expiry horizon, database disk growth, backup success/failure, and synthetic login/signature workflow checks.
Related internal guides
- Production Guide Deploy Nocodb With Docker Compose Traefik Postgresql On Ubuntu 267
- Production Guide Deploy Miniflux With Docker Compose Nginx And Postgresql On Ubuntu 260
- Production Guide Deploy Temporal With Kubernetes Helm Postgresql Ingress Nginx On Ubuntu 254
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.