Deploy a Self-Hosted Screenshot API on Railway
Prerequisites
- A Railway account (free to sign up; Hobby plan at $5/mo recommended for always-on deployments)
- Railway CLI installed:
npm install -g @railway/cli(optional — dashboard deployment works without it)
Option A: Deploy from the Railway dashboard (recommended)
Step 1: Create a new project
In the Railway dashboard, click New Project → Deploy a Docker image. Enter the Openkova image:
ghcr.io/scnix-git/openkova:latestRailway pulls the image and creates a service. It will show as failing until you add the required environment variables in the next step.
Step 2: Set environment variables
In your service settings, open the Variables tab and add:
CHROMIUM_PATH=/usr/bin/chromium
OPENKOVA_STORAGE_PATH=/data
PORT=3000CHROMIUM_PATH tells Openkova where the Chromium binary lives inside the Docker image. OPENKOVA_STORAGE_PATH sets the directory for generated screenshots. PORT tells Railway which port to expose.
Step 3: Add a persistent volume
In the service settings, open the Volumes tab and click Add Volume. Set the mount path to /data. This persists generated screenshots across restarts and redeployments.
Without a volume, screenshots are stored in the container's ephemeral filesystem and lost on each restart.
Step 4: Deploy and get your URL
Click Deploy. Railway builds the service, assigns a public URL (https://your-service.up.railway.app), and provisions automatic HTTPS. Once the health check passes, your screenshot API is live.
Test it:
curl -X POST https://your-service.up.railway.app/api/convert/snippet \
-H "Content-Type: application/json" \
-d '{"html": "<h1 style=\"font-family:sans-serif;padding:40px\">Hello Railway</h1>", "format": "png"}' \
--output test.pngOption B: Deploy via Railway CLI
If you prefer the CLI:
# Log in to Railway
railway login
# Create a new project
railway init
# Deploy the Docker image
railway up --image ghcr.io/scnix-git/openkova:latest
# Set environment variables
railway variables set CHROMIUM_PATH=/usr/bin/chromium
railway variables set OPENKOVA_STORAGE_PATH=/data
railway variables set PORT=3000
# Open the deployed service URL
railway openOption C: Deploy from your own GitHub fork
Fork the Openkova GitHub repository, then connect it to Railway via New Project → Deploy from GitHub repo. Railway detects the Dockerfile and builds the image on every push to your default branch. This is the best option if you want to run a customised fork.
Add a custom domain
In service settings, open the Networking tab and click Add Custom Domain. Railway generates a CNAME record — add it to your DNS provider and Railway provisions the SSL certificate automatically.
# Example: point api.yourdomain.com to Railway
CNAME api your-service.up.railway.appMemory and scaling
Headless Chromium uses approximately 150–300 MB per concurrent screenshot render. On the Railway Hobby plan ($5/mo), your services share a pool of 8 GB RAM — enough for a moderate-traffic screenshot API.
For high-concurrency workloads, Railway supports horizontal scaling (multiple replicas) and vertical scaling (reserved memory). A single Openkova instance handles approximately 5–15 concurrent screenshots comfortably on 512 MB reserved memory.
| Volume | Recommended setup | Estimated Railway cost |
|---|---|---|
| Low (<1K/day) | Single instance, 256 MB | ~$5/mo |
| Medium (1K–10K/day) | Single instance, 512 MB | ~$10–15/mo |
| High (>10K/day) | VPS (DigitalOcean/Hetzner) is more cost-effective | — |
Environment variable reference
| Variable | Value | Required |
|---|---|---|
CHROMIUM_PATH | /usr/bin/chromium | Yes |
OPENKOVA_STORAGE_PATH | /data | Yes (with volume) |
PORT | 3000 | Yes |
NEXT_PUBLIC_SITE_URL | Your Railway URL or custom domain | No |
Verify your deployment
Once deployed, confirm all three input modes are working:
BASE=https://your-service.up.railway.app
# HTML snippet → PNG
curl -X POST $BASE/api/convert/snippet \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Test</h1>","format":"png"}' --output snippet.png
# URL → PNG
curl -X POST $BASE/api/convert/url \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","format":"png"}' --output url.png
# HTML file → PDF
curl -X POST $BASE/api/convert/file \
-F "files=@report.html" -F "format=pdf" --output report.pdfFrequently asked questions
Can I deploy a screenshot API on Railway?
Yes. Openkova ships an official Docker image that deploys on Railway in under 10 minutes via New Project → Deploy a Docker image. Set CHROMIUM_PATH, OPENKOVA_STORAGE_PATH, and PORT as environment variables.
How much RAM does Openkova need on Railway?
Headless Chromium uses approximately 150–300 MB per concurrent screenshot. The Hobby plan ($5/mo) provides ample shared RAM for low-to-moderate traffic. Reserve 512 MB for consistent performance.
Does Railway persist files between deploys?
Not by default — containers have ephemeral filesystems. Add a Railway volume mounted at /data and set OPENKOVA_STORAGE_PATH=/data to persist screenshots.
Is Railway a good host for Openkova?
Railway is excellent for low-to-medium traffic — zero-ops, automatic HTTPS, easy custom domains, and usage-based billing. For high-volume screenshot generation (>10K/day), a dedicated VPS is more cost-effective.
Prefer a different platform? Openkova runs anywhere Docker runs.