swarza

Docs

Every application is an S3 bucket. Upload your build output (static files, a fetch app or Next.js), and swarza deploys it.

1. Create an application and a key

In the dashboard, create an application: its name is also its bucket name. Then create an access key on the application's Deploy tab. The secret is shown once. Keys are permanent until you revoke them, and each key works for one application only.

2. AWS CLI

aws configure set aws_access_key_id SW... --profile swarza
aws configure set aws_secret_access_key ... --profile swarza
aws configure set region eu-central-1 --profile swarza
aws configure set s3.addressing_style path --profile swarza

# Deploy (sync one folder that holds everything)
aws s3 sync ./out s3://my-app --delete --profile swarza --endpoint-url https://s3.staging.swarza.com

3. rclone

# ~/.config/rclone/rclone.conf
[swarza]
type = s3
provider = Other
access_key_id = SW...
secret_access_key = ...
endpoint = https://s3.staging.swarza.com
region = eu-central-1
force_path_style = true

rclone sync ./out swarza:my-app --checksum

4. Cyberduck

Open a connection of type Amazon S3, set the server to s3.staging.swarza.com, and paste your access key ID and secret. Your application appears as a single bucket.

5. GitHub Actions

# .github/workflows/deploy.yml
on: { push: { branches: [main] } }
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: aws s3 sync ./out s3://my-app --delete --endpoint-url https://s3.staging.swarza.com
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.SWARZA_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.SWARZA_SECRET }}
          AWS_DEFAULT_REGION: eu-central-1
          AWS_S3_ADDRESSING_STYLE: path

6. Folder layout

out/
  index.html, assets/…   # static files (or put them under publicDir)
  server.mjs             # optional: a fetch app (see below)
  swarza.json          # optional settings

swarza.json and .swarza/ are never served as files. Sync one folder that holds everything: aws s3 sync --delete of only dist/ would delete your application.

7. swarza.json

{
  "publicDir": "",
  "spa": true,
  "redirects": [{ "from": "/old/*", "to": "/new/:splat", "status": 301 }],
  "headers": [{ "source": "/*", "headers": { "X-Frame-Options": "DENY" } }]
}

Paths resolve in this order: redirects, the exact file, /index.html, .html (clean URLs), the SPA fallback, then your 404.html. Files with a hash in the name are cached for a year; HTML revalidates every time.

8. Fetch apps (Node.js and Bun)

Export a fetch handler, and swarza runs it on its own servers in real Node.js (the default) or Bun: node:* modules, node_modules, native packages and Bun.* APIs all work. Nothing runs while nobody visits, and a request is answered in milliseconds. Upload the application with its node_modules and a runtime section:

// index.mjs
export default {
  async fetch(request, env, ctx) {
    return Response.json({ hello: new URL(request.url).pathname });
  },
};

// swarza.json
{ "runtime": { "engine": "node", "entry": "index.mjs" } }

9. Next.js

Next.js 16.2 and later deploy with the swarza adapter: routing, middleware (Node.js and edge), Server Actions, streaming, ISR, on-demand revalidation and next/image. Files no middleware or rule touches are served without starting your application.

npm install --save-dev @swarza/next

// next.config.mjs
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
export default { adapterPath: require.resolve("@swarza/next") };

// build and upload
next build
aws s3 sync .swarza s3://my-app --delete

10. Databases

SQLite-compatible databases (the Turso engine) on the same servers as your applications: a query takes well under a millisecond, with no network in between. Create one under Databases in the dashboard and bind it to an application; fetch apps and Next.js applications get DATABASE_URL and DATABASE_AUTH_TOKEN. They speak libSQL: use @libsql/client, or Drizzle and other tools built on it.

import { createClient } from "@libsql/client/web";
import { drizzle } from "drizzle-orm/libsql/web";

const db = drizzle(createClient({
  url: process.env.DATABASE_URL,
  authToken: process.env.DATABASE_AUTH_TOKEN,
}));

Migrations and tools reach the database from anywhere with a token from its page (read-write or read-only, shown once):

DATABASE_URL=libsql://db.sites.staging.swarza.com \
DATABASE_AUTH_TOKEN=<token> \
npx drizzle-kit migrate      # dialect: "turso" in drizzle.config.ts

11. Storage

Buckets for your applications' files: uploads, avatars, documents. They speak S3, so the AWS SDK, the AWS CLI, rclone and Cyberduck work with them. Create one under Storage in the dashboard and bind it to an application. Every bucket has its own address, https://<bucket>.s3.staging.swarza.com: S3 clients connect there, and a file is at https://<bucket>.s3.staging.swarza.com/<key>. The application's fetch apps and Next.js applications get STORAGE_ENDPOINT, STORAGE_BUCKET, STORAGE_REGION, STORAGE_ACCESS_KEY_ID and STORAGE_SECRET_ACCESS_KEY.

import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  endpoint: process.env.STORAGE_ENDPOINT,
  region: process.env.STORAGE_REGION,
  credentials: {
    accessKeyId: process.env.STORAGE_ACCESS_KEY_ID,
    secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY,
  },
});

await s3.send(new PutObjectCommand({ Bucket: process.env.STORAGE_BUCKET, Key: "avatars/1.png", Body: file }));
const url = await getSignedUrl(s3,
  new GetObjectCommand({ Bucket: process.env.STORAGE_BUCKET, Key: "avatars/1.png" }), { expiresIn: 600 });

12. Scheduled jobs

Run your own code on a schedule: export a function from a module in your application, and list it with a five-field cron schedule (in UTC) in swarza.json. On each schedule swarza calls the function in its own sandboxed worker, with your app's environment variables and databases. It has no URL, so only swarza can start it. The function gets the same arguments as a Cloudflare Workers scheduled handler.

// jobs/cleanup.mjs
export default async function (controller, env, ctx) {
  console.log("cleaning up", controller.cron, new Date(controller.scheduledTime));
  ctx.waitUntil(sendReport()); // awaited before the run ends
}

// jobs/tasks.mjs
export async function digest(controller, env) { /* ... */ }

// swarza.json
{
  "runtime": { "entry": "index.mjs" },
  "scheduledJobs": [
    { "schedule": "0 3 * * *", "function": "jobs/cleanup.mjs" },
    { "schedule": "0 8 * * 1-5", "function": "jobs/tasks.mjs#digest" }
  ]
}

13. Deploys

14. Limits

Allowances are shared by all applications in your account; you can cap a single application in its Settings. Past 100% applications slow down instead of costing you more. See pricing.