Complete guide for deploying Papermark on Docker Swarm with Traefik.
- Docker Engine 20.10+ with Swarm mode enabled
- Traefik v2+ configured as reverse proxy
- Minimum 2GB RAM, 4GB recommended
- 20GB disk space minimum
- PostgreSQL Database (included in stack) OR external PostgreSQL
- S3-Compatible Storage (AWS S3, MinIO, Backblaze B2, etc.)
- Email Service (Resend recommended)
- Optional: Redis (included), Tinybird (analytics)
Ensure your domain points to your Docker Swarm cluster:
# Example DNS records
papermark.yourdomain.com A YOUR_SERVER_IPgit clone https://github.com/avnox-com/papermark-self-host.git
cd papermark-self-host# Copy example environment file
cp .env.example .env
# Edit with your values
nano .envMinimum required configuration:
# Core
PAPERMARK_PUBLIC_URL=https://papermark.yourdomain.com
PAPERMARK_DOMAIN=papermark.yourdomain.com
# Security
NEXTAUTH_SECRET=$(openssl rand -hex 32)
# Database
POSTGRES_PASSWORD=$(openssl rand -hex 32)
# Storage (choose one)
# Option 1: AWS S3
AWS_ACCESS_KEY_ID=your_key
AWS_SECRET_ACCESS_KEY=your_secret
AWS_S3_BUCKET_NAME=papermark-uploads
AWS_REGION=us-east-1
# Email
RESEND_API_KEY=re_xxxxxxxxxxxxx
# Authentication (at least one)
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret# If not already initialized
docker swarm init
# Create external network for Traefik
docker network create --driver=overlay traefik_publicFor advanced placement strategies:
# Label node for PostgreSQL
docker node update --label-add papermark.postgres=true $(docker node ls -q)
# Label node for backups
docker node update --label-add papermark.backup=true $(docker node ls -q)# Deploy to swarm
docker stack deploy -c docker-compose.papermark.yml papermark
# Check status
docker stack services papermark
docker stack ps papermarkBLOB_STORAGE_TYPE=s3
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_S3_BUCKET_NAME=my-papermark-bucket
AWS_REGION=us-east-1S3 Bucket Policy Example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-papermark-bucket/*",
"arn:aws:s3:::my-papermark-bucket"
]
}
]
}First, deploy MinIO:
# Add to docker-compose.papermark.yml
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- minio_data:/data
networks:
- papermark_internal
deploy:
mode: replicated
replicas: 1Then configure:
BLOB_STORAGE_TYPE=s3
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadmin
S3_ENDPOINT=http://minio:9000
S3_FORCE_PATH_STYLE=true
AWS_S3_BUCKET_NAME=papermark
AWS_REGION=us-east-1- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
https://papermark.yourdomain.com/api/auth/callback/google - Copy Client ID and Secret to
.env
- Go to GitHub Developer Settings
- Create new OAuth App
- Set Homepage URL:
https://papermark.yourdomain.com - Set Authorization callback URL:
https://papermark.yourdomain.com/api/auth/callback/github - Copy Client ID and Secret to
.env
- Sign up at Resend
- Verify your domain
- Create API key
- Add to
.env:
RESEND_API_KEY=re_xxxxxxxxxxxxx
EMAIL_FROM=noreply@yourdomain.com- Sign up at Tinybird
- Create a workspace
- Get API token
- Configure:
TINYBIRD_TOKEN=p.xxxxxxxxxxxxx
ENABLE_ANALYTICS=true- Create GitHub Repository Secrets:
# Navigate to: Settings > Secrets and variables > Actions
# Required secrets:
REGISTRY=ghcr.io
REGISTRY_USERNAME=your-github-username
REGISTRY_PASSWORD=your-github-token
IMAGE_PREFIX=ghcr.io/avnox-com
# Optional (for WireGuard):
REGISTRY_IP=10.0.0.1
WG_CONF=<your-wireguard-config>- Trigger Build:
# Push to main branch
git push origin main
# Or trigger manually
# Go to Actions tab > Build & Push Papermark > Run workflow- Monitor Build:
# Check GitHub Actions for build status
# Images will be pushed to your container registryIf you prefer to build locally:
# Clone Papermark
git clone https://github.com/mfts/papermark.git papermark-src
cd papermark-src
# Build image
docker build -f ../Dockerfile.papermark -t your-registry/papermark:latest .
# Push to registry
docker push your-registry/papermark:latestTo test or apply open PRs:
# In .github/workflows/build-and-push.yml
env:
PAPERMARK_PRS: "123,456,789" # PR numbers to merge# Pull latest image
docker service update --image ghcr.io/avnox-com/papermark:latest papermark_papermark
# Or update entire stack
docker stack deploy -c docker-compose.papermark.yml papermark# Scale Papermark horizontally
docker service scale papermark_papermark=4
# Or edit .env
PAPERMARK_REPLICAS=4# All services
docker stack ps papermark
# Specific service logs
docker service logs -f papermark_papermark
docker service logs -f papermark_postgres
# Follow logs with timestamps
docker service logs -f --timestamps papermark_papermarkBackups are automatic with the included postgres-backup service.
Manual Backup:
# Create backup
docker exec -it $(docker ps -q -f name=papermark_postgres) \
pg_dump -U papermark papermark > backup-$(date +%Y%m%d).sql
# Restore backup
docker exec -i $(docker ps -q -f name=papermark_postgres) \
psql -U papermark papermark < backup-20241027.sqlBackup Location:
Backups are stored in ./backups by default. Configure in .env:
BACKUP_PATH=/mnt/backups/papermark
BACKUP_SCHEDULE=@daily
BACKUP_KEEP_DAYS=7Migrations run automatically on startup. To run manually:
# Access Papermark container
docker exec -it $(docker ps -q -f name=papermark_papermark) sh
# Run migrations
npx prisma db push# Check service health
docker service ps papermark_papermark --no-trunc
# Check specific service
curl https://papermark.yourdomain.com/api/health# Service stats
docker stats $(docker ps -q -f name=papermark)
# Detailed service info
docker service inspect papermark_papermark --prettyAccess Traefik dashboard (if enabled) to monitor:
- Request rates
- Response times
- SSL certificates
- Service health
# Check service logs
docker service logs papermark_papermark --tail 100
# Check for errors
docker service ps papermark_papermark --no-trunc
# Inspect service configuration
docker service inspect papermark_papermark# Test database connectivity
docker exec -it $(docker ps -q -f name=papermark_postgres) \
psql -U papermark -d papermark -c "SELECT version();"
# Check DATABASE_URL format
echo $DATABASE_URL
# Should be: postgresql://user:pass@postgres:5432/papermark# Test S3 connectivity (for AWS)
aws s3 ls s3://your-bucket-name --profile papermark
# Test MinIO (if self-hosted)
docker exec -it $(docker ps -q -f name=minio) \
mc alias set local http://localhost:9000 minioadmin minioadmin# Verify Resend API key
curl -X GET https://api.resend.com/domains \
-H "Authorization: Bearer YOUR_RESEND_API_KEY"
# Check service logs for email errors
docker service logs papermark_papermark | grep -i resendGoogle OAuth:
- Verify redirect URI:
https://yourdom.com/api/auth/callback/google - Check Google Cloud Console for API quotas
- Ensure OAuth consent screen is published
GitHub OAuth:
- Verify callback URL matches exactly
- Check GitHub App settings
# Check Traefik logs
docker service logs traefik_traefik | grep -i certificate
# Verify DNS records
dig papermark.yourdomain.com
# Test ACME challenge
curl http://papermark.yourdomain.com/.well-known/acme-challenge/test-
Scale horizontally:
docker service scale papermark_papermark=4
-
Increase resources:
# In docker-compose resources: limits: cpus: "4" memory: 4G
-
Enable caching:
- Ensure Redis is running
- Configure Upstash Redis for better performance
-
Database optimization:
-- Run in PostgreSQL VACUUM ANALYZE; REINDEX DATABASE papermark;
-
Use strong secrets:
openssl rand -hex 32
-
Enable rate limiting (already configured in Traefik labels)
-
Regular updates:
# Update weekly docker service update --image ghcr.io/avnox-com/papermark:latest papermark_papermark -
Monitor logs for suspicious activity
-
Regular backups (automated in stack)
-
Use HTTPS only (enforced by Traefik)
# Comment out postgres service in docker-compose
# Update DATABASE_URL
DATABASE_URL=postgresql://user:pass@external-host:5432/dbnameRequires Vercel API integration:
ENABLE_CUSTOM_DOMAINS=true
PROJECT_ID_VERCEL=prj_xxxxx
TEAM_ID_VERCEL=team_xxxxx
AUTH_BEARER_TOKEN=xxxxxx# Development
docker stack deploy -c docker-compose.papermark.yml -c docker-compose.dev.yml papermark-dev
# Staging
docker stack deploy -c docker-compose.papermark.yml -c docker-compose.staging.yml papermark-staging
# Production
docker stack deploy -c docker-compose.papermark.yml papermark- GitHub Issues: mfts/papermark/issues
- Documentation: papermark.com/docs
- Community: Join discussions on GitHub
Papermark is licensed under AGPL-3.0. Some features may require enterprise license for commercial use.