~ $ man chasen

How Chasen works.

All of it, on one page: what runs where, what a deploy does step by step, where your data goes, and how the two programs talk. Nothing here is hidden behind an account.

[01] the architecture

Everything runs on your server. The bucket holds the copy.

One box runs the proxy, the Chasen API, and your apps. Each app is one container with its SQLite files next to it. Two things are outside the box, and both are yours: the registry that holds your images and the bucket that holds your backups.

A deploy goes from your computer over HTTPS to the proxy on your server and on to chasen-server, which pulls the image of your commit from your registry and starts your app. Visitors reach the app through the same proxy. The databases of the app are copied to your bucket.your server · Ubuntu, Docker, ports 80 and 443HTTPS and a token. No SSH.HTTPSapi.shop.each hour, a checked snapshot · each second, the new changespulls the imageyour computer$ chasen deploythe commit, chasen.yml, the secretsyour visitorsshop.example.coma browser, an API client, a webhookyour registryghcr.ioholds the image of each commityour bucketany S3-compatible storea new server gets its data back from hereproxy :80 :443kamal-proxyLet's Encrypt certificates · one domain, one container · no downtimethe APIchasen-serverpulls your imageasks /up, then swapsbacks up every databaseyour appshopone container/storage/db.sqlite3a websiteblogindex.htmlan addonlognorth*.sqlite3
  1. 1your computerchasen deploy says which commit to deploy, and sends chasen.yml and the secrets over HTTPS, with a token. No SSH.
  2. 2your registryghcr.io or Docker Hub. Your computer or your CI pushes the image of each commit there. Nothing builds on the server.
  3. 3proxykamal-proxy, with Let's Encrypt certificates. It sends each domain to its container.
  4. 4chasen-serverThe API. It pulls your image, asks /up, swaps the version, and backs up every database.
  5. 5your appsOne container each, with its SQLite files in /storage. Visitors reach them through the proxy.
  6. 6your bucketEach hour a checked snapshot, each second the new changes. A new server gets its data back from here.

[02] a deploy

What chasen deploy does, step by step.

To the server, a deploy is one HTTPS request. The server does the work and streams its output back, so you see each step as it runs. If your connection drops, the deploy still runs to its end.

  1. 1readsThe CLI reads chasen.yml and gets the values of your secrets, from your secret manager or from the environment. A missing secret stops here.
  2. 2buildsIt builds the image of the current git commit with your Docker, and pushes it to your registry: ghcr.io or Docker Hub.
  3. 3sendsIt sends one request to the API of your server, over HTTPS: the settings, the secrets, and the name of the image. There is no SSH.
  4. 4pullsThe server pulls that image from your registry: ghcr.io or Docker Hub, private or public. Nothing builds on the server.
  5. 5backs upIt copies every SQLite database of the app and checks each copy. A failed backup stops the deploy.
  6. 6startsIt starts the new container next to the old one and asks /up once a second.
  7. 7swapsOn the first 200, the proxy moves the traffic to the new container. The old one gets SIGTERM and 10 seconds to finish.
  8. 8or notNo 200 in 30 seconds: the server removes the new container. The old version never lost the traffic.

[03] the data

Your data is SQLite. That is the point.

A SQLite database is a file on the disk of the server. There is no database server to install, tune, upgrade, or pay for. A query never leaves the machine, so it is fast. A backup is a copy of a file.

One server with SQLite is only safe with backups you can restore, so Chasen keeps two kinds. Each hour and before each deploy, it makes a consistent copy of every database, runs an integrity check on the copy, and sends it to your bucket. Between two copies, Litestream sends the new changes to the bucket once a second.

  • Kept: the newest copy of each of the last 24 hours, 7 days, 4 weeks, and 6 months.
  • A restore deletes nothing. It moves the current databases aside first.
  • The server can die. Set up a new one with the same bucket and run chasen deploy. The data comes back first, about one second old.
  • Not SQLite? Uploaded files persist across deploys, but they have no backup yet. An app can use a Postgres that runs elsewhere.

[04] the protocol

One request for each command.

The API is small enough to call with curl. The command is in the path, the arguments are in the query, the request body is the input, and the response is the output as it happens. The last line is the exit code.

the API of your server
$ curl -X POST "https://api.example.com/v1/status?arg=shop" \
    -H "Authorization: Bearer $CHASEN_TOKEN"
App:      shop
Version:  3f9a2c1
State:    Up 2 hours
URL:      https://shop.example.com
Backup:   20261001T120000Z
Replica:  live
  • The login is the OAuth 2.0 device flow. You approve it in a browser with the token of the server, and the CLI gets a token of its own.
  • One owner for each server. Every login to a server can deploy every app on it.
  • In CI, set CHASEN_URL and CHASEN_TOKEN. There is no browser step.

[05] the parts

Boring, proven parts. Chasen is the small tool that holds them.

The server needs Docker and nothing else. There is no agent to keep alive, no control plane, and no hidden state: every file is in the list below.

what is underneath
Dockerruns each app in one container
your registryghcr.io or Docker Hub holds the image of each commit. Your computer or your CI builds it
kamal-proxyHTTPS, routing by domain, and the swap with no downtime. From 37signals' Kamal
Let's Encryptthe certificates, one for each domain, renewed by the proxy
SQLiteyour data, and the small database of the server itself
Litestreamstreams each database to your bucket. It runs inside chasen-server
matchathe deploy engine: it holds the proxy and the containers together
Gochasen and chasen-server are one static binary each
ls, on the server
/usr/local/bin/chasen-server        the binary. The API container runs it
/etc/chasen/config.yml              the base domain, the server token, the bucket
/etc/chasen/server.sqlite3          logins, and the history of every deploy
/etc/chasen/env/<app>.json          the env and the secrets of each app
/var/matcha/<app>/storage/          the storage of the app: /storage in the container
/var/matcha/<app>/backups/<time>/   the snapshots

[06] install

Seven lines, and the first app is live.

You need a server with Ubuntu or Debian, root access, and ports 80 and 443 open. Point a wildcard DNS record, *.example.com, to it first. Each app then gets its own name under that domain, with a certificate. On your computer you need Docker, git, and an account at ghcr.io or Docker Hub. The step-by-step guide →

on the server, as root
$ curl -fsSL https://chasenhq.com/server | sh
$ chasen-server setup --domain example.com
$ chasen-server bucket --endpoint https://... --name backups --access-key-id ...
on your computer
$ curl -fsSL https://chasenhq.com/cli | sh
$ chasen add server example.com
# opens a page. Type the token that setup printed on the server.
$ docker login ghcr.io
# once. The image goes to ghcr.io/you/shop, the name of your git origin on GitHub.
$ chasen deploy
Deployed shop 3f9a2c1
  https://shop.example.com
  • Both install scripts download one binary and check its checksum. To update the server, run both of its lines again: the install and chasen-server setup.
  • setup does not harden the server. chasen-server check reports what is missing: SSH with keys only, a firewall, and security updates.
  • Your app is an image in a registry. chasen deploy builds it where you run it and pushes it. Put the same command in GitHub Actions, and git push is the deploy.
  • Your app follows a short standard: an image, one port, /up, and its data in the storage. chasen check tests an app against it before it gets traffic.

[07] and the cloud?

The cloud is the same programs, on a server we set up.

The cloud adds one program, chasen-cloud. It speaks the same protocol as a server, creates the Hetzner server for you, and passes each command on to it. The server it creates runs the same chasen-server you can install yourself. So you can start on either side and change later: your apps are an image and a bucket.

diff self-hosted cloud
self-hostedcloud
the programschasen, chasen-serverthe same two, plus chasen-cloud in front
the serveryours, from any providera Hetzner server that we create, in Europe or the USA
setupyou, with one commanddone at the first deploy
hardeningyou. chasen-server check lists what is missingdone at the first deploy
DNSyou: one wildcard recorddone
the backup bucketyours, one commanddone
log inchasen add server <domain>chasen login
price€0€9 a month, plus the server

That is all of it. One server is plenty for the rest of us.