Deploy a Self-Hosted Screenshot API on Fly.io
.fly.dev subdomain — deployed in approximately 10 minutes using the official Docker image and flyctl.Prerequisites
- A Fly.io account — free to sign up, credit card required to launch apps
flyctlinstalled:curl -L https://fly.io/install.sh | shthenfly auth login
Step 1: Create the Fly app
Create a new directory for your Fly config and initialise the app:
mkdir openkova-fly && cd openkova-fly
fly launch --image ghcr.io/scnix-git/openkova:latest --no-deploy--no-deploy creates the app and generates a fly.toml without deploying yet — we need to configure memory and volumes first.
When prompted:
- App name: choose any unique name (e.g.
openkova-yourname) - Region: pick the region closest to your app servers (not your end users — screenshot requests are server-to-server)
- Postgres / Redis: No
Step 2: Configure fly.toml
Open the generated fly.toml and replace the [http_service] and [[vm]] sections with the following. The key requirements are 512 MB RAM (Chromium minimum) and the /data volume mount:
# fly.toml
app = "openkova-yourname"
primary_region = "syd" # change to your region
[build]
image = "ghcr.io/scnix-git/openkova:latest"
[env]
PORT = "3000"
CHROMIUM_PATH = "/usr/bin/chromium"
OPENKOVA_STORAGE_PATH = "/data"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = false
auto_start_machines = true
min_machines_running = 1
[[mounts]]
source = "openkova_data"
destination = "/data"
[[vm]]
memory = "512mb"
cpu_kind = "shared"
cpus = 1Important: Set auto_stop_machines = false and min_machines_running = 1. Screenshot APIs need to respond immediately — a cold-start delay of 5–10 seconds from a suspended machine will cause timeouts in your application.
Step 3: Create the persistent volume
fly volumes create openkova_data --size 1 --region sydReplace syd with your chosen region. The volume name must match the source in your fly.toml mounts section. 1 GB is sufficient for most workloads — increase with fly volumes extend if needed.
Step 4: Deploy
fly deployFly pulls the Docker image, attaches the volume, and starts the machine. The first deploy takes about 2–3 minutes as the image is pulled. Subsequent deploys are faster.
Once deployed, Fly assigns a .fly.dev URL with automatic HTTPS:
fly status
# App: openkova-yourname
# Hostname: openkova-yourname.fly.devStep 5: Test your deployment
BASE=https://openkova-yourname.fly.dev
# HTML snippet → PNG
curl -X POST $BASE/api/convert/snippet \
-H "Content-Type: application/json" \
-d '{"html":"<h1 style=\"font-family:sans-serif;padding:40px\">Hello Fly.io</h1>","format":"png"}' \
--output test.png
# URL → PNG
curl -X POST $BASE/api/convert/url \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","format":"png"}' \
--output screenshot.pngAdd a custom domain
# Point your domain to Fly
fly certs add api.yourdomain.com
# Fly will show you the DNS record to add:
# CNAME api openkova-yourname.fly.devAdd the CNAME to your DNS provider. Fly provisions the SSL certificate automatically once the DNS propagates (usually within a few minutes).
Scaling and concurrency
A single Fly machine with 512 MB RAM handles approximately 3–8 concurrent screenshots. For higher throughput, either increase the machine size or scale to multiple machines:
# Scale to 2 machines for higher concurrency
fly scale count 2
# Increase to 1 GB RAM for demanding workloads
fly scale memory 1024| Machine size | RAM | Concurrent screenshots | Approx. cost/mo |
|---|---|---|---|
| shared-cpu-1x | 512 MB | 3–5 | ~$3–5 |
| shared-cpu-1x | 1 GB | 6–10 | ~$6–10 |
| shared-cpu-2x | 2 GB | 12–20 | ~$15–20 |
Viewing logs
# Stream live logs
fly logs
# View logs for a specific machine
fly logs --machine <machine-id>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 .fly.dev URL or custom domain | No |
Frequently asked questions
Can I deploy a screenshot API on Fly.io?
Yes. Openkova's Docker image deploys on Fly.io with fly deploy. Configure memory = "512mb" in your fly.toml, set CHROMIUM_PATH=/usr/bin/chromium, and attach a volume at /data.
Why does Openkova need 512 MB RAM on Fly.io?
Headless Chromium requires approximately 150–300 MB per concurrent render. Below 512 MB total, the container risks OOM-killing Chromium mid-screenshot. 512 MB is the practical minimum for reliable single-concurrent operation.
Which region should I use for a screenshot API on Fly.io?
Deploy in the same region as your application servers. Screenshot requests are server-to-server — your app POSTs to Openkova — so the latency that matters is app → Openkova, not user → Openkova.
Do I need auto-stop disabled for a screenshot API?
Yes. Set auto_stop_machines = false. If Fly suspends the machine between requests, the first screenshot after a cold start will be delayed 5–15 seconds as the machine restarts — enough to timeout most HTTP clients.
Prefer a different platform? Openkova runs anywhere Docker runs.