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" } }
engineisnode(Node.js 24) orbun.entryis a path in your upload (.mjs,.js,.cjsor, with type stripping in Node.js and natively in Bun,.ts).- The entry exports
{ fetch }, an app with afetchmethod (Hono, Elysia) or a function.request.urlis the visitor'shttps://URL; their address is inX-Forwarded-For.ctx.waitUntil()accepts work that finishes after the response. - Your files are read-only; write temporary files to
/tmp(64 MB). Outbound requests to the internet work; listening on ports doesn't. - A worker stays warm for 60 seconds after the last request, is replaced after 10,000 requests, and is stopped if a request gets no answer within 30 seconds (504). Keep state in a database, not in memory.
- Memory is 256 MB, 512 MB or 1 GB by plan, and warm time counts against the plan's app compute allowance. Every request goes to your handler; set
Cache-Controlon responses you want cached at the edge. - Environment variables set in the dashboard (the application's Environment tab) are in
process.envand inenv, together with your databases. Changing one restarts the application within seconds.
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
- The build runs Vercel's official Next.js adapter, which writes the standard Build Output format, and packs it into
.swarza/. Monorepos and pnpm work. Any other framework that writes.vercel/outputdeploys withnpx swarza-build-output. - Your application starts on the first request in well under a second and sleeps after a minute without requests. Prerendered and regenerated pages and
revalidatePath/revalidateTagare kept in your application's store, so they survive restarts and show at once. - Native modules other than sharp need a build on Linux arm64.
output: "export"is an application with only static files: upload theoutfolder.
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
- As many databases as you need, on every plan, holding up to 10 GB together (all of your account's databases). At the limit, writes that need more space fail; reading, updating and deleting still work, and deleted rows make room again. One database can serve several applications (each binding has its own name,
<NAME>_URLand<NAME>_AUTH_TOKEN, and can be read-only), and all of an application's workers share it. - Download a database as a SQLite file with any of its tokens:
curl -H "Authorization: Bearer <token>" https://db.sites.staging.swarza.com/download -o my.db. - Every write is backed up within about a second. Restore to any moment of the last 30 days from the database's page; the restore replaces the database, and the state before it stays in the backups. Deleted databases are kept 30 days.
- Writes are queued and applied one at a time, in about 30 microseconds each, so keep transactions short; reads run in parallel. A crash of the server can lose at most the last 0.2 seconds of writes.
- Use
@libsql/client/web(plain JavaScript) in applications you upload: the default entry loads a native module that would need a build on Linux arm64.
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 });
- Public buckets let anyone read a file at its address, as S3 does (bound applications get the bucket's address as
STORAGE_PUBLIC_URL), through the CDN. The CDN caches a file for as long as itsCache-Controlsays, so upload with one (for names that never change,public, max-age=31536000, immutable); without it, every request reads the file from the bucket. Private buckets answerAccessDeniedwithout a key: they are read with a key or with signed links. Switch between them on the bucket's page. - Your own domain, such as
files.example.com: add it on the bucket's page and point a CNAME at the address shown there; the certificate follows in a few minutes. To sign links for it, give the SDK that domain as the bucket's endpoint:new S3Client({ endpoint: "https://files.example.com", bucketEndpoint: true, ... })withBucket: "https://files.example.com". - From your machine or CI, use a key from the bucket's page:
aws s3 cp photo.jpg s3://<bucket>/photos/ --endpoint-url https://s3.staging.swarza.com. - Every S3 request counts toward your plan's requests, and downloads toward its transfer, as for your applications. Buckets share your plan's storage with your applications' files; files can be as large as your plan allows. Buckets are counted from their uploads within seconds: once your storage is full, they stop taking uploads within seconds (deleting still works), and a file larger than your plan allows is removed after its upload. A deleted file is gone at once; a deleted bucket takes its files and keys with it.
- A bucket's name is also an address, so bucket and application names are one list: a name used by one can't be used by the other.
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" } ] }
- Fetch apps:
functionis a module in your upload (.mjs,.jsor.ts), with#namefor a named export; without it, the default export (a function, or an object withscheduled). Next.js: putswarza.jsonnext tonext.config.mjswith paths in your project ("jobs/cleanup.ts"); the adapter bundles each job with what it imports. - Hobby: 5 jobs, at most every 15 minutes, 1 minute per run. Starter: 50, every minute, 5 minutes. Pro: 100, every minute, 15 minutes. A run over its time is stopped. A deploy that asks for more jobs, runs them more often, or names a module that isn't in the upload fails with a clear message.
- A run starts within its scheduled minute (not at second 0). One still going when the next is due is skipped. Runs count against your app compute allowance (memory × time); while your account is over its allowances, or the application is capped, jobs pause.
- The application's Scheduled jobs tab shows the next and last runs, with Run now. What the function prints and each run's result are in its Logs tab. Set either the day of the month or the day of the week, not both.
13. Deploys
- Automatic: a deploy starts about 15 seconds after your last upload, once no multipart upload is still open.
- Manual: upload
.swarza/deploy(echo go | aws s3 cp - s3://my-app/.swarza/deploy), or click Deploy in the dashboard. Turn automatic deploys off in Settings. - Visitors see the old version or the new one, never a mix. Roll back to any kept deployment with one click.
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.