This guide walks through deploying PDFTools to the web, from a simple one-click platform all the way to a production VPS with a custom domain, HTTPS, and a CDN.
Vercel deploys the React SPA with a global CDN in about two minutes.
Push your code to GitHub (already done).
pdf-tools repo.frontendSet environment variables (Settings → Environment Variables):
| Key | Value |
|---|---|
VITE_API_URL |
https://api.yourdomain.com |
Deploy — Vercel runs npm run build and serves dist/.
yourdomain.com and www.yourdomain.comEvery push to main triggers a new deployment. Preview deployments are created for each pull request.
pdf-tools.frontendnpm run buildfrontend/distVITE_API_URL.netlify.toml (already works without this, but enables headers):[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Add this file at frontend/netlify.toml if you use Netlify.
Use this for full control, including running the backend on the same server.
| Provider | Entry plan | Location |
|---|---|---|
| DigitalOcean Droplets | $6/mo (1 vCPU, 1 GB RAM) | Toronto, New York, etc. |
| Hetzner Cloud | €4/mo (2 vCPU, 2 GB RAM) | Cheapest option |
| Vultr | $6/mo | Multiple regions |
# Create a new Ubuntu 22.04 server, then SSH in:
ssh root@YOUR_SERVER_IP
# Update system
apt update && apt upgrade -y
# Install Docker + Docker Compose
curl -fsSL https://get.docker.com | sh
apt install -y docker-compose-plugin
# Install Nginx + Certbot (for SSL)
apt install -y nginx certbot python3-certbot-nginx
# Install Node.js 20 (optional, for running without Docker)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
git clone https://github.com/ppsk2011/pdf-tools.git
cd pdf-tools
cp backend/.env.example backend/.env
nano backend/.env # Fill in your values
docker compose up --build -d
This starts:
frontend container on port 3000 (nginx serving static files)backend container on port 3001 (Node.js API)redis container on port 6379 (internal only)# /etc/nginx/sites-available/pdftools
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect www → apex
if ($host = www.yourdomain.com) {
return 301 https://yourdomain.com$request_uri;
}
# Frontend (React SPA)
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Backend API
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 110M; # match MAX_FILE_SIZE + overhead
proxy_read_timeout 120s;
}
# Health check
location /health {
proxy_pass http://localhost:3001/health;
}
}
ln -s /etc/nginx/sites-available/pdftools /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
# Replace with your actual domain
certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Certbot auto-configures nginx and sets up auto-renewal
systemctl enable certbot.timer
After this, your site is live at https://yourdomain.com with a free, auto-renewing certificate.
Route 53 (DNS)
│
▼
CloudFront (CDN + HTTPS termination)
├── /api/* → Application Load Balancer → ECS Fargate (backend)
└── /* → S3 Static Website (frontend)
│
ElastiCache (Redis)
# Build
cd frontend
VITE_API_URL=https://api.yourdomain.com npm run build
# Create S3 bucket
aws s3 mb s3://pdftools-frontend --region ca-central-1
# Enable static website hosting
aws s3 website s3://pdftools-frontend \
--index-document index.html \
--error-document index.html
# Upload
aws s3 sync dist/ s3://pdftools-frontend --delete \
--cache-control "public,max-age=31536000,immutable" \
--exclude "index.html"
aws s3 cp dist/index.html s3://pdftools-frontend/index.html \
--cache-control "no-cache"
# Create CloudFront distribution pointing to the S3 bucket
# (do this in AWS Console or via CDK/Terraform)
# Build & push to ECR
aws ecr create-repository --repository-name pdftools-api
aws ecr get-login-password | docker login --username AWS \
--password-stdin <ACCOUNT_ID>.dkr.ecr.ca-central-1.amazonaws.com
docker build -t pdftools-api ./backend
docker tag pdftools-api <ACCOUNT_ID>.dkr.ecr.ca-central-1.amazonaws.com/pdftools-api:latest
docker push <ACCOUNT_ID>.dkr.ecr.ca-central-1.amazonaws.com/pdftools-api:latest
# Create ECS cluster, task definition, and service via AWS Console
# or Infrastructure as Code (see infra/ directory)
Cloud Run is serverless and scales to zero — great for cost-efficient deployments.
# Authenticate
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
# Build & push backend
gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/pdftools-api ./backend
# Deploy
gcloud run deploy pdftools-api \
--image gcr.io/YOUR_PROJECT_ID/pdftools-api \
--platform managed \
--region northamerica-northeast1 \
--allow-unauthenticated \
--memory 1Gi \
--concurrency 80 \
--set-env-vars NODE_ENV=production,FRONTEND_URL=https://yourdomain.com
The backend is a standard Node.js/Express app packaged as a Docker image. It can run anywhere Docker runs.
docker build -t pdftools-api ./backend
docker run -d \
--name pdftools-api \
--restart unless-stopped \
-p 3001:3001 \
--env-file backend/.env \
pdftools-api
npm install -g pm2
cd backend && npm install
pm2 start src/server.js --name pdftools-api --instances max
pm2 save
pm2 startup # generates systemd service
| Option | Notes |
|---|---|
Docker (included in docker-compose.yml) |
Development / small deployments |
| AWS ElastiCache | Managed, multi-AZ, auto-failover |
| Redis Cloud (free tier 30 MB) | Easiest external option |
| Upstash (serverless Redis) | Pay-per-request, good for Cloud Run |
# Upstash example
REDIS_URL=rediss://:password@global-xxx.upstash.io:6380
Copy backend/.env.example to backend/.env and fill in every value before deploying.
| Variable | Required | Example |
|---|---|---|
PORT |
No | 3001 |
NODE_ENV |
Yes | production |
FRONTEND_URL |
Yes | https://yourdomain.com |
MAX_FILE_SIZE |
No | 104857600 |
FILE_TTL_MS |
No | 1800000 |
STRIPE_SECRET_KEY |
For payments | sk_live_... |
STRIPE_WEBHOOK_SECRET |
For payments | whsec_... |
REDIS_URL |
Yes | redis://localhost:6379 |
For the frontend, set VITE_API_URL as a build-time env var:
VITE_API_URL=https://api.yourdomain.com npm run build