Tutorial  ·  June 2026

Deploy a Self-Hosted Screenshot API on Fly.io

What you'll have: Openkova running on Fly.io with a persistent volume, automatic HTTPS, and a .fly.dev subdomain — deployed in approximately 10 minutes using the official Docker image and flyctl.

Prerequisites

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:

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 = 1

Important: 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 syd

Replace 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 deploy

Fly 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.dev

Step 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.png

Add 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.dev

Add 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 sizeRAMConcurrent screenshotsApprox. cost/mo
shared-cpu-1x512 MB3–5~$3–5
shared-cpu-1x1 GB6–10~$6–10
shared-cpu-2x2 GB12–20~$15–20

Viewing logs

# Stream live logs
fly logs

# View logs for a specific machine
fly logs --machine <machine-id>

Environment variable reference

VariableValueRequired
CHROMIUM_PATH/usr/bin/chromiumYes
OPENKOVA_STORAGE_PATH/dataYes (with volume)
PORT3000Yes
NEXT_PUBLIC_SITE_URLYour .fly.dev URL or custom domainNo

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.

More deployment options

Prefer a different platform? Openkova runs anywhere Docker runs.

Deploy on Railway →Deploy on Render →Docker Compose guide →