Zum Inhalt springen
CASOON

Bun-Apps auf Fly.io deployen

Container-basiertes Deployment für Anwendungen mit mehr Ressourcenbedarf

6 Minuten
Bun-Apps auf Fly.io deployen
#Bun #Fly.io #Docker #Deployment
SerieBun: Die moderne JavaScript-Runtime
Teil 5 von 5

Cloudflare Workers sind ideal für leichtgewichtige Edge-Funktionen. Aber manchmal braucht man mehr: persistente Verbindungen, größere Bundles, SQLite-Dateien oder längere Ausführungszeiten. Fly.io füllt diese Lücke.

Warum Fly.io?

Fly.io führt Container auf Servern weltweit aus – näher an den Nutzern als klassisches Hosting, aber mit mehr Möglichkeiten als reine Edge-Functions:

FeatureCloudflare WorkersFly.io
AusführungszeitMax. 30s (paid)Unbegrenzt
Memory128 MBBis 8 GB
Persistente VolumesNeinJa
WebSocketsEingeschränktVoll
SQLite-DateienNeinJa
Cold Starts~0ms~300ms

Projekt-Setup

Ein minimales Bun-Projekt mit Hono:

mkdir my-api && cd my-api
bun init -y
bun add hono
// src/index.ts
import { Hono } from "hono";
import { logger } from "hono/logger";

const app = new Hono();

app.use("*", logger());

app.get("/", (c) => {
  return c.json({ 
    message: "Hello from Fly.io",
    region: process.env.FLY_REGION || "local"
  });
});

app.get("/health", (c) => c.text("ok"));

export default {
  port: process.env.PORT || 3000,
  fetch: app.fetch,
};

Dockerfile für Bun

FROM oven/bun:1-alpine AS base
WORKDIR /app

# Dependencies installieren
FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production

# Build (falls nötig)
FROM base AS build
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
# RUN bun run build

# Production Image
FROM base AS runtime
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NODE_ENV=production
EXPOSE 3000

CMD ["bun", "run", "src/index.ts"]

Das Alpine-Image ist ~100 MB – deutlich kleiner als Node.js-Images.

Fly.io Konfiguration

# CLI installieren
brew install flyctl

# Einloggen
fly auth login

# App erstellen
fly launch

Die generierte fly.toml:

app = "my-bun-api"
primary_region = "fra"

[build]

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = "stop"
  auto_start_machines = true
  min_machines_running = 0

[checks]
  [checks.health]
    type = "http"
    port = 3000
    path = "/health"
    interval = "10s"
    timeout = "2s"

[[vm]]
  memory = "256mb"
  cpu_kind = "shared"
  cpus = 1

Deployment

fly deploy

Die App läuft jetzt auf https://my-bun-api.fly.dev.

Environment Variables

# Einzelne Variable
fly secrets set DATABASE_URL="postgres://..."

# Aus .env-Datei
fly secrets import < .env.production

Im Code:

const dbUrl = process.env.DATABASE_URL;

Persistente Volumes

Für SQLite oder Datei-Storage:

# Volume erstellen (1 GB in Frankfurt)
fly volumes create data --size 1 --region fra

In fly.toml:

[mounts]
  source = "data"
  destination = "/data"

Im Code:

import { Database } from "bun:sqlite";

const db = new Database("/data/app.db");

Wichtig: Volumes sind an eine Region gebunden. Bei Multi-Region-Deployments braucht jede Region ihr eigenes Volume.

Multi-Region Deployment

# Zusätzliche Region hinzufügen
fly scale count 2 --region fra,iad

Fly.io routet Requests automatisch zur nächsten Region.

Read Replicas mit LiteFS

Für SQLite mit Multi-Region:

[mounts]
  source = "litefs"
  destination = "/litefs"

LiteFS repliziert SQLite-Änderungen automatisch zwischen Regionen.

Skalierung

# Horizontal: Mehr Machines
fly scale count 3

# Vertikal: Mehr Ressourcen
fly scale memory 512
fly scale vm shared-cpu-2x

Auto-Scaling basiert auf auto_stop_machines und auto_start_machines.

Logs und Monitoring

# Live Logs
fly logs

# Metriken im Dashboard
fly dashboard

Bun + SQLite auf Fly.io

Ein vollständiges Beispiel mit der nativen SQLite-Integration:

import { Hono } from "hono";
import { Database } from "bun:sqlite";

const db = new Database("/data/app.db");

// Schema erstellen
db.run(`
  CREATE TABLE IF NOT EXISTS visits (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    path TEXT,
    region TEXT,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

const logVisit = db.prepare(
  "INSERT INTO visits (path, region) VALUES (?, ?)"
);
const getStats = db.prepare(`
  SELECT path, COUNT(*) as count 
  FROM visits 
  GROUP BY path 
  ORDER BY count DESC 
  LIMIT 10
`);

const app = new Hono();

app.use("*", async (c, next) => {
  logVisit.run(c.req.path, process.env.FLY_REGION || "local");
  await next();
});

app.get("/stats", (c) => {
  return c.json(getStats.all());
});

export default {
  port: process.env.PORT || 3000,
  fetch: app.fetch,
};

Private Networking

Fly.io Apps können sich über ein privates Netzwerk erreichen:

// Service-to-Service Kommunikation
const response = await fetch("http://other-app.internal:3000/api");

Keine öffentliche Exposition nötig.

Vergleich: Wann was nutzen?

AnwendungsfallEmpfehlung
Stateless API, globalCloudflare Workers
API mit SQLiteFly.io
WebSocket-ServerFly.io
Lange BerechnungenFly.io
Minimale KostenCloudflare (Free Tier)
Persistente DatenFly.io mit Volumes

Quellen

Realistische Kosten und Performance

Aus konkreten Production-Setups Anfang 2026:

  • Fly.io Hobby (1 shared CPU, 256 MB RAM): Free Tier deckt kleine Tools. Pricing ab 2 USD/Monat für persistente Apps.
  • Fly.io Standard (1 vCPU, 2 GB RAM): ca. 10–15 USD/Monat. Reicht für mittlere Web-Apps mit Datenbank.
  • Fly.io Performance (2 vCPU, 8 GB RAM): ca. 30–60 USD/Monat. Für ressourcenhungrige Anwendungen.

Latenzen aus mehreren Regionen (Stand 2025):

  • EU (Frankfurt) → Fly.io Frankfurt: 15–30 ms
  • EU → Fly.io US-East: 100–150 ms
  • Globale App mit Multi-Region: 15–80 ms je nach Nutzer-Standort

Wann Fly.io vs Cloudflare Workers

  • Fly.io: Wenn Apps persistente Datenbank brauchen, längere Cold-Start-Zeiten OK, komplexere Setups.
  • Cloudflare Workers: Wenn Stateless-Logik, niedrige Latenz, sehr hohe Last (bis zu 200k req/s/Worker).
  • Beide kombinieren: Workers für Edge-Logik, Fly.io für Database-bezogene Anwendungen.

Häufige Fallstricke

  • Region-Ausfälle: Fly.io hat mehrfach pro Jahr Region-Ausfälle. Multi-Region-Setup ist Pflicht für kritische Apps.
  • Resource-Limits: Process-Speicher und Volume-Größe haben Limits. Vor Production prüfen.
  • Build-Time-Limits: Sehr große Builds (>10 Min) können fehlschlagen. Multi-Stage-Builds optimieren.

Wann Fly.io nicht die richtige Wahl ist

  • Bei strengen Compliance-Anforderungen: AWS, Azure haben mehr Audit-Zertifikate.
  • Bei sehr großem Traffic mit stabilem Pattern: Klassisches AWS oder GCP kann günstiger sein.
  • Bei Teams mit existierender AWS/GCP-Expertise: Migration zu Fly.io ist Lernkurve, nicht immer gerechtfertigt.