~ $ cat docs/deploy.md

Deploy

cd myapp
chasen deploy

The deploy pulls the image, makes a backup, starts the new container, and moves the traffic when /up answers. If the new version does not become healthy in time, the old version keeps the traffic.

A deploy runs to its end on the server, also when your connection drops.

The image

An app is an image in a registry.

A repository on GitHub needs no setting. When the git origin is github.com/you/shop, the image goes to ghcr.io/you/shop, and Chasen uses the login that is already on your computer: docker login ghcr.io (with your GitHub name and a token that has the write:packages scope), or the gh CLI (gh auth login -s write:packages). In CI it is GHCR_TOKEN or GITHUB_TOKEN. For every other registry, docker login is enough too.

For another registry, or another name, chasen.yml says it, without a tag:

name: shop
image: you/shop           # Docker Hub. Or registry.example.com/you/shop
registry:                 # the login of the registry
  username: you
  password: REGISTRY_TOKEN   # the name of a secret, not its value

chasen deploy then does three things: it builds the image of the current git commit with your Docker, pushes it to the registry, and tells the server to pull it. The tag is the full hash of the commit.

  • The build gets the files of the commit, not your working directory. So the image is what its tag says.
  • In CI it is the same command. Deploy from GitHub Actions has the workflow, the two secrets it needs, and what to do when it fails. With it, git push is the deploy.
  • The login of the registry is used for the push, and it goes with the deploy for the pull. The server does not keep it. In GitHub Actions it is the token of the job, so there is no long-lived token to store.
  • chasen deploy --tag <tag> deploys an image that is already in the registry, and builds nothing. This is the rollback (--tag <older commit>), and the way to deploy an image that another system built.
  • The architecture. The build is for linux/amd64, like most servers. For another server, set DOCKER_DEFAULT_PLATFORM.

Websites

A directory with an index.html and no Dockerfile deploys as a static website. Chasen serves the files of the commit with Caddy. A site that needs a build step (Astro, Hugo) is an image like any app: it has a Dockerfile like any app.

Addons

An addon is a ready product that Chasen runs from its image. There is no source and no build. It gets a domain, HTTPS, and the same backups as your apps.

chasen enable lognorth                    # https://lognorth.example.com
chasen enable fusionaly stats.example.com # with a domain of your own
chasen -a lognorth status                 # -a names the app: an addon has no directory
chasen -a lognorth logs
Addon What it is
fusionaly Privacy-first web analytics
formlander Form backend for static sites
lognorth Logs, errors, alerts, and uptime

Run chasen enable <addon> again to update it to the newest image. An addon has one domain.

History

chasen history
# ID  WHEN (UTC)           ACTION                      RESULT
# 5   2026-10-01 12:03:21  deploy 6cff7df              failed
# 4   2026-10-01 12:03:02  restore                     succeeded
# 3   2026-10-01 12:02:44  domains add shop.com        succeeded
chasen history 5      # the full output of that deploy

The server records each deploy, restore, domain change, and removal, with its output.

chasen.yml

The file is optional for a website, and for an app whose git origin is on GitHub. Without name:, the app name is the name of the directory. Without image:, the image is ghcr.io/<owner>/<repository> of the git origin.

name: myapp
env:
  LOG_LEVEL: info
secrets: [STRIPE_KEY, SMTP_PASSWORD]
secrets_command: fnox export

# Overrides of the standard. Leave them out when the defaults fit.
port: 3000
health: /_health
health_timeout: 90
volumes: [/app/storage]

Secrets

Secret values never go into git or into the image. secrets: lists the names. At each deploy, Chasen reads the values on your machine and sends them to the server over HTTPS. A missing secret stops the deploy before it changes anything.

Chasen reads each value from the output of secrets_command, then from the environment. secrets_command is any command that prints KEY=VALUE lines:

Tool secrets_command
fnox fnox export
1Password op inject -i .env.tpl
sops (encrypted file in git) sops -d secrets.enc.env
A local file cat .env.production

Without secrets_command, wrap the deploy: fnox exec -- chasen deploy or op run --env-file=.env.tpl -- chasen deploy.

On the server, the values are in files that only root can read.

To change a value or rotate a secret without a build:

chasen restart       # starts the app again with the env and secrets of chasen.yml

Secrets travel with the deploy on purpose. Nothing that matters lives only on the server, so a new server needs one chasen deploy to get the configuration, the secrets, and the data back.

Next: The app standard →This page on GitHub →