Self-Hosting DocuSeal: Open Source Document Signing Platform
If you’ve ever paid $25/month for DocuSign just to get a few contracts signed, you know the pain. DocuSeal is an open source alternative that handles document creation, filling, and signing — all self-hosted on your own server. No per-envelope fees, no subscription tiers, no sending your sensitive contracts through someone else’s infrastructure.
It’s a Ruby on Rails app with a clean, mobile-optimized interface that works surprisingly well for something you can spin up in five minutes with Docker.
What DocuSeal Does
- WYSIWYG PDF form builder — drag and drop fields onto any PDF
- 12 field types — signature, date, text, checkbox, file upload, initials, and more
- Multiple signers per document — assign fields to different parties
- Automated email delivery — send documents for signature via SMTP
- Bulk sending — import recipients from CSV or XLSX spreadsheets
- Template system — create reusable document templates
- API and webhooks — integrate signing into your own apps
- Embedded signing — React, Vue, Angular, and vanilla JS components
- PDF signature verification — cryptographic verification of completed documents
- White-label — custom logo and branding
- SSO/SAML support — enterprise identity providers
- Conditional fields and formulas — dynamic forms that adapt
- SMS verification — identity verification via text message
- 14 signing languages — international support out of the box
DocuSeal vs The Alternatives
| Feature | DocuSeal | DocuSign | SignWell | OpenSign |
|---|---|---|---|---|
| Self-hosted | ✅ Yes | ❌ No | ❌ No | ✅ Yes |
| Open source | ✅ AGPLv3 | ❌ No | ❌ No | ✅ AGPLv3 |
| Form builder | ✅ WYSIWYG | ✅ Yes | ✅ Yes | ⚠️ Basic |
| Multiple signers | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| API | ✅ REST API | ✅ Yes | ✅ Yes | ⚠️ Limited |
| Embedded signing | ✅ React/Vue/JS | ✅ Yes | ✅ Yes | ❌ No |
| Bulk send | ✅ CSV/XLSX | ✅ Yes | ✅ Yes | ❌ No |
| Template HTML API | ✅ Yes | ❌ No | ❌ No | ❌ No |
| SMS verification | ✅ Yes | ✅ Yes | ❌ No | ❌ No |
| Price | Free (self-hosted) | $25+/mo | $8+/mo | Free (self-hosted) |
| Storage options | Disk/S3/GCS/Azure | Cloud only | Cloud only | Cloud/S3 |
DocuSeal wins on features-per-dollar. The embedded signing components and HTML template API make it particularly strong for developers integrating document signing into their own products.
Prerequisites
- A Linux server (VPS or home server) with at least 1GB RAM
- Docker and Docker Compose installed (our Docker guide)
- A domain name pointed at your server (for HTTPS)
- SMTP credentials for sending signature request emails (optional but recommended)
Installation with Docker Compose
Create a directory for DocuSeal:
mkdir -p ~/docuseal && cd ~/docuseal
Create your compose.yaml:
services:
docuseal:
image: docuseal/docuseal:latest
container_name: docuseal
ports:
- "3000:3000"
volumes:
- docuseal-data:/data
environment:
- SECRET_KEY_BASE=your-secret-key-here
# Uncomment for PostgreSQL instead of SQLite:
# - DATABASE_URL=postgresql://docuseal:password@db:5432/docuseal
restart: unless-stopped
volumes:
docuseal-data:
Generate a secret key:
openssl rand -hex 64
Replace your-secret-key-here with the generated key.
Start the container:
docker compose up -d
DocuSeal will be available at http://your-server-ip:3000. The first user to sign up becomes the admin.
Production Setup with PostgreSQL
For production use, swap SQLite for PostgreSQL:
services:
docuseal:
image: docuseal/docuseal:latest
container_name: docuseal
ports:
- "3000:3000"
volumes:
- docuseal-data:/data
environment:
- DATABASE_URL=postgresql://docuseal:${DB_PASSWORD}@db:5432/docuseal
- SECRET_KEY_BASE=${SECRET_KEY}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
container_name: docuseal-db
volumes:
- docuseal-db:/var/lib/postgresql/data
environment:
- POSTGRES_USER=docuseal
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=docuseal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U docuseal"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
docuseal-data:
docuseal-db:
Create a .env file:
DB_PASSWORD=$(openssl rand -hex 24)
SECRET_KEY=$(openssl rand -hex 64)
echo "DB_PASSWORD=$DB_PASSWORD" > .env
echo "SECRET_KEY=$SECRET_KEY" >> .env
SMTP Configuration
DocuSeal needs SMTP to email signature requests. Go to Settings → Email in the web UI after your first login, or set environment variables:
environment:
- SMTP_ADDRESS=smtp.gmail.com
- SMTP_PORT=587
- [email protected]
- SMTP_PASSWORD=your-app-password
- [email protected]
- SMTP_DOMAIN=yourdomain.com
For Gmail, use an app password. For production, services like Amazon SES, Resend, or Mailgun are more reliable.
Creating Your First Template
- Log in and click New Template
- Upload a PDF document (contract, NDA, invoice, etc.)
- Use the WYSIWYG builder to place fields:
- Signature — draw or type signature fields
- Date — auto-populated or manual date fields
- Text — free-form text input
- Checkbox — agreement checkboxes
- Initials — initial fields for multi-page documents
- File — attachment upload fields
- Select — dropdown selections
- Radio — multiple choice options
- Stamp — image/stamp uploads
- Cells — structured data entry
- Phone — phone number fields
- Payment — Stripe payment fields
- Assign fields to submitters (First Party, Second Party, etc.)
- Save the template
Using the HTML API for Templates
For programmatic template creation, DocuSeal supports an HTML-to-PDF API:
curl -X POST "http://localhost:3000/api/templates/html" \
-H "X-Auth-Token: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Service Agreement</h1><p>This agreement is between {{company}} and {{client}}.</p><p>Signature: {{signature}}</p>",
"name": "Service Agreement"
}'
This is incredibly powerful for generating dynamic documents from your own application.
Sending Documents for Signature
- Open a template and click Send
- Add submitter email addresses
- Customize the email message (optional)
- Click Send — each signer receives an email with a unique signing link
Bulk Sending
For high-volume signing (onboarding, compliance forms):
- Open a template
- Click Bulk Send
- Upload a CSV or XLSX with columns matching your template fields
- DocuSeal sends individual signing requests to each row
Storage Configuration
By default, files are stored on the local disk in the /data volume. For production, configure cloud storage:
AWS S3
environment:
- AWS_ACCESS_KEY_ID=your-key
- AWS_SECRET_ACCESS_KEY=your-secret
- AWS_REGION=us-east-1
- AWS_S3_BUCKET=your-docuseal-bucket
Google Cloud Storage
environment:
- GCS_BUCKET=your-docuseal-bucket
- GCS_CREDENTIALS={"type":"service_account",...}
Azure Blob Storage
environment:
- AZURE_STORAGE_ACCOUNT_NAME=youraccount
- AZURE_STORAGE_ACCESS_KEY=your-key
- AZURE_STORAGE_CONTAINER=docuseal
Reverse Proxy Configuration
Caddy (Recommended)
Add to your Caddyfile:
If running DocuSeal with Caddy in the same Docker network, remove the ports mapping and use Docker networking instead. See our Caddy reverse proxy guide for details.
Nginx
server {
listen 443 ssl http2;
server_name docs.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/docs.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/docs.yourdomain.com/privkey.pem;
client_max_body_size 50M;
location / {
proxy_pass http://docuseal: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;
}
}
Note the client_max_body_size 50M — you’ll need this for uploading large PDF documents.
API Integration
DocuSeal’s REST API lets you integrate document signing into any application. Get your API token from Settings → API.
Create a Submission via API
curl -X POST "http://localhost:3000/api/submissions" \
-H "X-Auth-Token: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"template_id": 1,
"send_email": true,
"submitters": [
{
"role": "First Party",
"email": "[email protected]"
}
]
}'
Embedded Signing with React
npm install @docuseal/react
import { DocusealForm } from '@docuseal/react'
function SigningPage({ submissionId }) {
return (
<DocusealForm
src={`https://docs.yourdomain.com/s/${submissionId}`}
onComplete={(data) => console.log('Signed!', data)}
/>
)
}
Components are also available for Vue, Angular, and vanilla JavaScript.
Webhooks
Configure webhooks in Settings → Webhooks to receive notifications when documents are:
- Viewed by a signer
- Completed and signed
- All parties have finished signing
User Management
DocuSeal supports role-based access:
- Admin — full access, manage users and settings
- Editor — create and manage templates, send documents
- Viewer — view completed submissions only
Go to Settings → Users to invite team members.
Backup and Restore
SQLite (Default)
The SQLite database lives in the /data volume:
# Backup
docker cp docuseal:/data ./docuseal-backup-$(date +%Y%m%d)
# Restore
docker compose down
docker cp ./docuseal-backup-20260321/. docuseal:/data/
docker compose up -d
PostgreSQL
# Backup database
docker exec docuseal-db pg_dump -U docuseal docuseal > backup-$(date +%Y%m%d).sql
# Backup uploaded files
docker cp docuseal:/data ./docuseal-files-backup
# Restore
docker exec -i docuseal-db psql -U docuseal docuseal < backup-20260321.sql
docker cp ./docuseal-files-backup/. docuseal:/data/
For automated backups, check our Kopia backup guide.
Updating
cd ~/docuseal
docker compose pull
docker compose up -d
DocuSeal handles database migrations automatically on startup.
Troubleshooting
Emails Not Sending
- Verify SMTP settings in Settings → Email
- Check container logs:
docker logs docuseal - Test with a simple SMTP provider first (Gmail app password works for testing)
- Some providers block port 25 — use 587 (TLS) or 465 (SSL) instead
Large PDF Upload Fails
- Increase Nginx
client_max_body_size(default 1M is too small) - For Caddy, there’s no default limit — check available disk space
- Ensure your
/datavolume has enough storage
Signature Fonts Not Rendering
- DocuSeal bundles its own fonts, but custom fonts need the
/data/fontsdirectory - Restart the container after adding fonts:
docker compose restart
Database Connection Errors (PostgreSQL)
- Ensure the
dbcontainer is healthy before DocuSeal starts (usedepends_onwith healthcheck) - Check PostgreSQL logs:
docker logs docuseal-db - Verify
DATABASE_URLformat:postgresql://user:password@host:5432/database
Container Runs Out of Memory
- DocuSeal with SQLite runs fine on 512MB RAM
- PostgreSQL adds ~256MB overhead
- Large PDF processing can spike memory — allocate at least 1GB total
- Set memory limits in compose:
deploy.resources.limits.memory: 1g
API Authentication Fails
- API tokens are per-user — check you’re using the right one
- Token header is
X-Auth-Token, notAuthorization: Bearer - Regenerate the token in Settings if it stops working
Power User Tips
- Custom branding — upload your logo in Settings for white-labeled signing pages
- Template tags — use text tags in PDFs (
{{field_name}}) to auto-place fields via the API, skipping the WYSIWYG builder entirely - DOCX templates — upload Word documents with field tags for dynamic document generation
- Conditional fields — show/hide fields based on other field values (great for complex contracts)
- Formulas — auto-calculate values (totals, dates, concatenated strings)
- Stripe payments — add payment fields to collect money alongside signatures
- Audit trail — every completed document includes a certificate of completion with timestamps, IP addresses, and signature hashes
Wrapping Up
DocuSeal hits the sweet spot for self-hosted document signing. The WYSIWYG form builder makes it accessible to non-technical users, while the REST API and embedded components make it a developer’s dream for building signing workflows into custom applications.
For most self-hosters, the Docker + SQLite setup is plenty. Scale up to PostgreSQL and S3 storage when you’re processing hundreds of documents. Either way, you’re looking at zero per-envelope costs and full control over your sensitive documents.
Useful links: