Self-hosted document signing (DocuSign alternative) with DocuSeal and Docker Compose.
Last updated: Oct 2026
DocuSeal is an open-source document signing platform. Upload a PDF, place signature and form fields, and send it out for signing. Completed documents are stored with an audit trail.
Why you'd want it: Sign leases, contracts and permission slips without paying per envelope or handing documents to a third party.
| Item | Value |
|---|---|
| Stack folder | /opt/stacks/docuseal |
| Category | Documents |
| Main image | docuseal/docuseal:latest |
| Services | 1 |
| Web UI | https://docuseal.<YOUR_DOMAIN> |
| Official docs | https://www.docuseal.com/docs |
.env/.secret files from Docker Folders and Environmentdocuseal database and user)FORCE_SSL=true expects HTTPS)Folder layout:
/opt/stacks/
├── _shared/
│ ├── .env # shared, non-secret settings
│ └── .secret # passwords, tokens and keys (chmod 600, never commit)
└── docuseal/
└── compose.yaml
/opt/appdata/docuseal/ # persistent application data
/opt/stacks/docuseal/compose.yaml:
services:
app:
image: docuseal/docuseal:latest
env_file:
- /opt/stacks/_shared/.env
- /opt/stacks/_shared/.secret
ports:
- 3000:3000
volumes:
- /opt/appdata/docuseal:/data/docuseal
environment:
- HOST=docuseal.<YOUR_DOMAIN>
- FORCE_SSL=true
- DATABASE_URL=postgresql://<DB_USER>:<DB_PASSWORD>@<DB_HOST>:5432/docuseal
- SMTP_ADDRESS=<SMTP_HOST>
- SMTP_PORT=25
- SMTP_FROM=<ADMIN_EMAIL>
- SMTP_ENABLE_STARTTLS=false
Compose reads these from the shared files with --env-file. Replace every <PLACEHOLDER> with your own value.
/opt/stacks/_shared/.env:
PUID=1000
PGID=1000
TZ=<TZ>
| Variable | File | Purpose | Example |
|---|---|---|---|
PUID |
.env |
User ID the container runs as (see Docker Folders) | 1000 |
PGID |
.env |
Group ID the container runs as | 1000 |
TZ |
.env |
Time zone in tz database format, e.g. America/New_York | <TZ> |
| Host port | Container port | Protocol | Service | Purpose |
|---|---|---|---|---|
| 3000 | 3000 | tcp | app |
Web UI |
| Host path | Container path | Service | Purpose |
|---|---|---|---|
/opt/appdata/docuseal |
/data/docuseal |
app |
Application data |
No limits are set. This app is light, so add deploy.resources.limits only if you need to cap it.
sudo mkdir -p /opt/stacks/docuseal /opt/appdata/docuseal
sudo chown -R $USER: /opt/stacks/docuseal
/opt/stacks/_shared/.env and /opt/stacks/_shared/.secret (chmod 600 .secret)./opt/stacks/docuseal/compose.yaml.cd /opt/stacks/docuseal
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret config --quiet
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret up -d
docker compose logs -f.CREATE DATABASE docuseal; CREATE USER docuseal WITH PASSWORD '<DB_PASSWORD>'; GRANT ALL ON DATABASE docuseal TO docuseal;http://<SERVER_IP>:3000.Tip: create a small wrapper such as
alias dc='docker compose --env-file /opt/stacks/_shared/.env --env-file /opt/stacks/_shared/.secret'so every stack uses the same env files.
cd /opt/stacks/docuseal
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret pull
docker compose --env-file ../_shared/.env --env-file ../_shared/.secret up -d
docker image prune -f
Back up /opt/appdata/docuseal before major upgrades. Watchtower can automate updates for stateless apps.
docker compose ps and docker compose logs --tail=100.FORCE_SSL=true needs the proxy to send X-Forwarded-Proto: https.SMTP_ADDRESS, SMTP_PORT and whether your relay needs STARTTLS or authentication.DATABASE_URL and that the database user owns the public schema.:?err): run the config command above to see which variable is not set in your env files.Educational example. Review every path, port and credential before you deploy, and never publish your .secret file.