Documenso supports two storage backends for document files:
Database - Store files directly in PostgreSQL (default)
S3 - Store files in Amazon S3 or S3-compatible services
Storage Comparison
Feature Database S3 Setup Complexity Simple Moderate Best For Small deployments, getting started Production, large files, scalability Cost Database storage costs S3 storage + API costs Scalability Limited by database size Unlimited Backup Included in database backup Separate backup needed CDN Support No Yes (CloudFront, etc.)
Database Storage (Default)
Files are stored as binary data (bytea) in the PostgreSQL database.
Configuration
NEXT_PUBLIC_UPLOAD_TRANSPORT = "database"
Pros
Simple setup - No additional services required
ACID guarantees - Files and metadata always in sync
Single backup - Database backup includes files
No external dependencies
Cons
Database bloat - Large files increase database size
Performance - Can slow down database queries
Scalability - Limited by database storage capacity
Cost - Database storage typically more expensive than object storage
Recommended For
Development and testing
Small deployments (<1000 documents)
Simplified backup requirements
When S3 costs/complexity are not justified
S3 Storage
Files are stored in Amazon S3 or S3-compatible object storage (MinIO, Wasabi, Cloudflare R2, DigitalOcean Spaces, etc.).
Configuration
NEXT_PUBLIC_UPLOAD_TRANSPORT
Set to "s3" to enable S3 storage. NEXT_PUBLIC_UPLOAD_TRANSPORT = "s3"
NEXT_PRIVATE_UPLOAD_BUCKET
Name of the S3 bucket to store documents. NEXT_PRIVATE_UPLOAD_BUCKET = "documenso-documents"
NEXT_PRIVATE_UPLOAD_REGION
string
default: "us-east-1"
AWS region where the bucket is located. NEXT_PRIVATE_UPLOAD_REGION = "us-east-1"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID
AWS access key ID or S3-compatible access key. NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY
AWS secret access key or S3-compatible secret key. NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
NEXT_PRIVATE_UPLOAD_ENDPOINT
Custom S3 endpoint for S3-compatible providers (MinIO, Wasabi, etc.). Not needed for AWS S3. NEXT_PRIVATE_UPLOAD_ENDPOINT = "https://s3.wasabisys.com"
NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE
Use path-style URLs instead of virtual-hosted-style. Required for some S3-compatible providers.
false (default): https://bucket.domain.com/path
true: https://domain.com/bucket/path
NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE = "true"
CloudFront/CDN Support (Optional)
NEXT_PRIVATE_UPLOAD_DISTRIBUTION_DOMAIN
CloudFront distribution domain for serving files. NEXT_PRIVATE_UPLOAD_DISTRIBUTION_DOMAIN = "https://d1234567890.cloudfront.net"
NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_ID
CloudFront key pair ID for signing URLs. NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_ID = "APKAEXAMPLE"
NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_CONTENTS
Private key for CloudFront signed URLs. NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_CONTENTS = "-----BEGIN PRIVATE KEY-----\n..."
Provider-Specific Setup
AWS S3
MinIO
Cloudflare R2
DigitalOcean Spaces
Wasabi
1. Create S3 Bucket aws s3 mb s3://documenso-documents --region us-east-1
The bucket should be private. Documenso uses pre-signed URLs for access. {
"Version" : "2012-10-17" ,
"Statement" : [
{
"Sid" : "DenyPublicAccess" ,
"Effect" : "Deny" ,
"Principal" : "*" ,
"Action" : "s3:GetObject" ,
"Resource" : "arn:aws:s3:::documenso-documents/*" ,
"Condition" : {
"StringNotEquals" : {
"aws:PrincipalArn" : "arn:aws:iam::ACCOUNT_ID:user/documenso"
}
}
}
]
}
3. Create IAM User aws iam create-user --user-name documenso
4. Attach Policy {
"Version" : "2012-10-17" ,
"Statement" : [
{
"Effect" : "Allow" ,
"Action" : [
"s3:PutObject" ,
"s3:GetObject" ,
"s3:DeleteObject"
],
"Resource" : "arn:aws:s3:::documenso-documents/*"
}
]
}
aws iam put-user-policy --user-name documenso \
--policy-name S3Access \
--policy-document file://documenso-s3-policy.json
5. Create Access Keys aws iam create-access-key --user-name documenso
NEXT_PUBLIC_UPLOAD_TRANSPORT = "s3"
NEXT_PRIVATE_UPLOAD_BUCKET = "documenso-documents"
NEXT_PRIVATE_UPLOAD_REGION = "us-east-1"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
1. Install MinIO docker run -d \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
-v /data:/data \
minio/minio server /data --console-address ":9001"
2. Create Bucket Access MinIO Console at http://localhost:9001 and create a bucket named documenso-documents. Or use the CLI: mc alias set local http://localhost:9000 minioadmin minioadmin
mc mb local/documenso-documents
3. Create Access Keys In the MinIO Console:
Go to Access Keys
Create New Access Key
Save the access key and secret
NEXT_PUBLIC_UPLOAD_TRANSPORT = "s3"
NEXT_PRIVATE_UPLOAD_BUCKET = "documenso-documents"
NEXT_PRIVATE_UPLOAD_REGION = "us-east-1"
NEXT_PRIVATE_UPLOAD_ENDPOINT = "http://localhost:9000"
NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE = "true"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID = "your-access-key"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY = "your-secret-key"
1. Create R2 Bucket In Cloudflare Dashboard:
Go to R2
Create bucket named documenso-documents
2. Create API Token
Go to R2 > Manage R2 API Tokens
Create API Token
Select “Object Read & Write” permissions
Save the Access Key ID and Secret Access Key
3. Get Account ID Your R2 endpoint format is: https://<account-id>.r2.cloudflarestorage.com
NEXT_PUBLIC_UPLOAD_TRANSPORT = "s3"
NEXT_PRIVATE_UPLOAD_BUCKET = "documenso-documents"
NEXT_PRIVATE_UPLOAD_REGION = "auto"
NEXT_PRIVATE_UPLOAD_ENDPOINT = "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID = "your-r2-access-key-id"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY = "your-r2-secret-access-key"
Cloudflare R2 has no egress fees, making it cost-effective for document downloads.
1. Create Space In DigitalOcean Control Panel:
Go to Spaces
Create a Space named documenso-documents
Choose a region (e.g., nyc3)
2. Create API Key
Go to API > Spaces Keys
Generate New Key
Save the Access Key and Secret Key
NEXT_PUBLIC_UPLOAD_TRANSPORT = "s3"
NEXT_PRIVATE_UPLOAD_BUCKET = "documenso-documents"
NEXT_PRIVATE_UPLOAD_REGION = "nyc3"
NEXT_PRIVATE_UPLOAD_ENDPOINT = "https://nyc3.digitaloceanspaces.com"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID = "your-spaces-key"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY = "your-spaces-secret"
1. Create Bucket In Wasabi Console:
Go to Buckets
Create Bucket named documenso-documents
Choose a region (e.g., us-east-1)
2. Create Access Keys
Go to Access Keys
Create New Access Key
Save the Access Key and Secret Key
NEXT_PUBLIC_UPLOAD_TRANSPORT = "s3"
NEXT_PRIVATE_UPLOAD_BUCKET = "documenso-documents"
NEXT_PRIVATE_UPLOAD_REGION = "us-east-1"
NEXT_PRIVATE_UPLOAD_ENDPOINT = "https://s3.wasabisys.com"
NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID = "your-wasabi-access-key"
NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY = "your-wasabi-secret-key"
Testing Storage Configuration
After configuring storage, test it by uploading a document:
Start Documenso
Create a new document
Check the logs for any S3 errors
Verify the file appears in your S3 bucket
Migrating Between Storage Backends
Changing storage backends requires migrating existing files. This is a manual process.
From Database to S3
Set up S3 storage configuration
Create a migration script to:
Query all document data from the database
Upload files to S3
Update database records with S3 keys
Switch NEXT_PUBLIC_UPLOAD_TRANSPORT to s3
Restart Documenso
A migration tool may be added to Documenso in the future. For now, contact support if you need assistance migrating.
Security Best Practices
Use Private Buckets
Never make your S3 bucket publicly accessible. Documenso uses pre-signed URLs for secure access.
Enable Versioning
Enable bucket versioning to protect against accidental deletion: aws s3api put-bucket-versioning \
--bucket documenso-documents \
--versioning-configuration Status=Enabled
Enable Encryption
Enable server-side encryption: aws s3api put-bucket-encryption \
--bucket documenso-documents \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}]
}'
Set Up Lifecycle Rules
Configure lifecycle policies to manage old versions and reduce costs: {
"Rules" : [
{
"Id" : "DeleteOldVersions" ,
"Status" : "Enabled" ,
"NoncurrentVersionExpiration" : {
"NoncurrentDays" : 90
}
}
]
}
aws s3api put-bucket-lifecycle-configuration \
--bucket documenso-documents \
--lifecycle-configuration file://lifecycle.json
Monitor Access
Enable S3 access logging or CloudTrail to audit file access.
Troubleshooting
”Invalid upload transport” error
Cause: NEXT_PUBLIC_UPLOAD_TRANSPORT is not set to s3 when trying to use S3 storage.
Solution: Set NEXT_PUBLIC_UPLOAD_TRANSPORT="s3".
”Access Denied” errors
Cause: Insufficient IAM permissions or incorrect credentials.
Solution: Verify the IAM user/role has s3:PutObject, s3:GetObject, and s3:DeleteObject permissions.
”Bucket not found” error
Cause: Bucket doesn’t exist or wrong region.
Solution: Create the bucket in the specified region:
aws s3 mb s3://documenso-documents --region us-east-1
Connection timeout with MinIO/custom endpoint
Cause: Incorrect endpoint URL or network issues.
Solution:
Verify the endpoint is accessible from your Documenso instance
Check NEXT_PRIVATE_UPLOAD_ENDPOINT is correct
Set NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE="true" for MinIO
Use CloudFront for Downloads
Configure CloudFront to cache document downloads:
Create a CloudFront distribution with your S3 bucket as origin
Configure signed URLs for security
Set appropriate cache TTLs
Configure Documenso with CloudFront variables
Regional Deployment
For global deployments:
Create S3 buckets in multiple regions
Use geo-routing to direct users to the nearest bucket
Consider S3 Cross-Region Replication for redundancy
Next Steps
Email Configuration Set up email delivery
Database Configuration Optimize database settings