swarza

Docs

Build your application (static files, a fetch app or Next.js) and deploy the folder with one command, from the dashboard, or from GitHub. Every deploy is a snapshot you can roll back to.

1. Deploy

Create an application in the dashboard (its name is also its address), then deploy from the folder your build wrote. The first command opens your browser to sign in; the CLI keeps a token for next time.

npx @swarza/cli login
npx @swarza/cli deploy --app my-app --prod

2. Previews

Without --prod, swarza deploy makes a preview named after your git branch (or --preview <name>) at https://my-app--<name>.sites.stg.swarza.com. Deploying the same name again updates it. Production doesn't change.

3. Dashboard upload

On the application's Deploy tab, drop your build folder or a .zip (or choose one), as production or as a named preview. The browser zips a folder before it uploads it.

4. GitHub Actions

Pushes to your main branch go live; every pull request gets the preview pr-<number>, a comment with its address and a GitHub deployment, and closing it removes the preview. Create a token under Tokens in the dashboard (limit it to the application) and add it to the repository as the SWARZA_TOKEN secret.

# .github/workflows/deploy.yml
on:
  push: { branches: [main] }
  pull_request:
    types: [opened, synchronize, reopened, closed]
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions: { contents: read, deployments: write, pull-requests: write }
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
        if: github.event.action != 'closed'
      - uses: swarza/deploy-action@v1
        with:
          token: ${{ secrets.SWARZA_TOKEN }}
          app: my-app

Anywhere else, run the CLI with the token: SWARZA_TOKEN=… npx @swarza/cli deploy --prod.

5. 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. Deploy the one folder that holds everything: each deploy replaces the whole application.

6. 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.

7. 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" } }

8. 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 deploy
next build
npx @swarza/cli deploy --app my-app --prod

9. 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.stg.swarza.com \
DATABASE_AUTH_TOKEN=<token> \
npx drizzle-kit migrate      # dialect: "turso" in drizzle.config.ts

10. 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.stg.swarza.com: S3 clients connect there, and a file is at https://<bucket>.s3.stg.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 });

11. 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" }
  ]
}

12. Deploys

13. 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.