Deploying / Directus
Directus
Installing the Directus template -- a headless CMS and data API on SQLite -- and signing in for the first time.
Directus wraps a SQL database with a real-time API and an admin app for its content. The template runs it on SQLite, so it needs nothing but the machine: no accessory, no external database.
| Image | directus/directus:11.17.4 |
| Port | 8055, behind kamal-proxy |
| Health check | /server/health |
| Volumes | database, uploads, extensions |
| Singleton | yes |
Before you start
- A server that is registered, and a ready runner
- A hostname. Directus builds its links from its public URL, so the environment is blocked
until it has one. If you leave the domain blank and the server has an IP address, KamalCloud makes
up a
*.sslip.iohostname that already resolves to the server, so this rarely blocks.
Installing
Templates → Directus → Install. The form asks for:
| Field | |
|---|---|
| Name | the app’s name, which is also the Kamal service and the prefix of every volume |
| Runner | where Kamal runs |
| Environment | production unless you want something else |
| Server | the machine Directus runs on |
| Domain | the hostname it is served on, e.g. cms.example.com. Left blank, it gets an sslip.io hostname |
| Admin email | the first account. Defaults to admin@example.com – change it |
Install creates the app and does not deploy it. If you typed a domain, point an A record for
it at the server, then press Deploy.
What the install sets
Plain variables, in the destination config:
| Variable | Value |
|---|---|
PUBLIC_URL |
https:// plus the environment’s first domain, filled in at each deploy |
DB_CLIENT |
sqlite3 |
DB_FILENAME |
/directus/database/data.db |
WEBSOCKETS_ENABLED |
true |
IP_TRUST_PROXY |
true, because kamal-proxy is always in front |
ADMIN_EMAIL |
what you typed on the form |
Secrets, generated once at install:
| Secret | |
|---|---|
KEY |
random base64. Directus reads it at every boot |
SECRET |
random base64, used to sign sessions and tokens |
ADMIN_PASSWORD |
32 random letters and digits |
Regenerating KEY or SECRET signs everyone out and invalidates every token Directus has
issued, so leave them alone once the app is in use.
PUBLIC_URL is not stored. It is worked out from the domain each time KamalCloud renders the
config, so changing the domain and deploying again is enough to move it.
Signing in
- Open Environment → Variables and read
ADMIN_PASSWORD. Secret values are shown to admins only. - Go to
https://<your domain>/adminand sign in with the admin email and that password.
Changing the admin password
Directus reads ADMIN_PASSWORD once: on the first boot, against an empty database. On every
boot after that it skips the step, so editing the secret changes nothing.
To change it for real, open Environment → Shell – a shell on the machine, not in the container – and run the Directus CLI inside the running container:
docker exec -it $(docker ps -q --filter label=service=<app name> | head -1) \
npx directus users passwd --email <admin email> --password '<new password>'
Then update ADMIN_PASSWORD too, so the variable does not mislead the next person who reads it.
Data and backups
The three volumes are registered for backup at install, each named
<app name>-<volume> on the host:
- database – the SQLite file. It is marked to stop containers: Directus writes to it
continuously, and a live
tarof SQLite does not restore. Each backup of it is a short outage. - uploads – files added through the admin app
- extensions – anything you install into
/directus/extensions
None of them is scheduled until you set a schedule and pick a bucket on the volume.
Upgrading
The template is pinned to 11.17.4, the last release of the 11 line, and that is deliberate. Directus 12 enforces licensing on self-hosted instances: they run on a free Core tier where SSO, custom permission rules on access policies and self-hosted LLM connections stop working without a paid licence, and a new instance is enforced from its first boot.
11.17.4 has none of that, and it will also get no further fixes. If you move to 12, change the image tag on the app and deploy. Because the database, uploads and extensions each have their own volume, the new image picks up all three. Take a backup of the database volume first.
Troubleshooting
- Blocked: “Directus reads its own address” – the environment has no domain. Install
normally adds an
sslip.ioone, so this happens when the server had no IP address at install or the domain was removed. Add one under Domains and deploy. - The admin password does not work – you probably changed
ADMIN_PASSWORDafter the first boot. Use the CLI above. - Two containers during a deploy – they should not overlap. As a singleton the app deploys with no drain time, because two Directus processes writing one SQLite file is worse than a short gap.