Skip to content

Self-hosted PaaS

The repository ships three templates under deploy/paas/: a Coolify compose resource, a Dokploy template and a CapRover one-click app. All three are derived from deploy/compose.http.yml, so each runs the same published image, keeps its data on a volume at /data, reads the same config file and listens on port 8765. A drift test (packages/server/test/deploy-paas-templates.test.ts) checks each template against the compose bundle: the image, the environment, a read-write /data mount, the config file, the port, the absence of a healthcheck, and how each platform fills the public URL and the JWT secret. It does not deploy anything.

Item Value
Image ghcr.io/the-40-thieves/obsidian-tc:latest, published for linux/amd64 and linux/arm64
Volume one persistent volume mounted at /data
Config file /config/obsidian-tc.config.json, with the shipped HTTP settings below
OBSIDIAN_TC_CONFIG /config/obsidian-tc.config.json
OBSIDIAN_TC_VAULT_PATH /data/vault
OBSIDIAN_TC_CACHE_DIR /data/cache
PORT 8765
OBSIDIAN_TC_PUBLIC_URL the https:// origin of the platform’s domain
OBSIDIAN_TC_JWT_SECRET generated by the platform, 64 characters
Health check inherited from the image: HEALTHCHECK probes /healthz. No compose healthcheck is added

The config file sets two things that have no environment overlay. transports.http.host must be 0.0.0.0, so the proxy can reach the server, and transports.stdio must be false. It also sets auth.mode to jwt with auth.requireJti on. The other settings are described in Railway’s config file and environment variables, which apply here unchanged.

OBSIDIAN_TC_PUBLIC_URL must be https://host or https://host:port, with no path, query or credentials. Anything else refuses to boot. Some platform defaults are plain http: attach an https domain to the service before you deploy, and set the public URL to that origin.

The JWT templates do not use the bundled authorization server. The config’s auth.as setting has no environment overlay and must name the public https origin, which a static template cannot know. The templates therefore sign tokens with the generated secret, as compose.http.yml does. To switch to the bundled server, see Tokens and the bundled authorization server.

  1. In Coolify, create a new resource from a Docker Compose source and paste the contents of deploy/paas/coolify/obsidian-tc.yaml.
  2. Set the domain on the obsidian-tc service. Its SERVICE_URL_OBSIDIAN_TC_8765 variable routes port 8765 through that domain.
  3. Deploy. Coolify generates SERVICE_PASSWORD_64_OBSIDIANTC, which is the JWT secret, and writes the config file from the content: block of the bind mount before the container starts.

To change the config, edit the file in Coolify’s storage view and restart the service, because the server reads the file at boot.

  1. Create a Compose service and import the template. The files are deploy/paas/dokploy/docker-compose.yml, template.toml and meta.json. If your Dokploy version does not import a template folder, paste docker-compose.yml into the service and copy the values from template.toml by hand: the OBSIDIAN_TC_PUBLIC_URL and OBSIDIAN_TC_JWT_SECRET entries, the obsidian-tc domain on port 8765, and the config file.
  2. Dokploy generates the domain and a 64-character password. The config file is mounted from ../files/obsidian-tc.config.json, which the template writes.
  3. Before the first deploy, open the domain’s settings and set its certificate to Let’s Encrypt with HTTPS on. Dokploy creates the generated domain without a certificate, so until you do this the service answers only over plain HTTP, while OBSIDIAN_TC_PUBLIC_URL is an https:// address.
  4. Deploy, then check that the service answers on its https domain.
  5. To use your own domain instead, point the domain’s host at it, set OBSIDIAN_TC_PUBLIC_URL to https://<your domain>, and redeploy. The generated URL is written at import, so it does not follow a domain change. If the server refuses the new Host header, add the domain to transports.http.allowedHosts in the config file.
  1. Add deploy/paas/caprover/obsidian-tc.yml as a one-click app. The file is a CapRover template, so use the template entry for pasting a YAML definition.
  2. Enable HTTPS for the app after the first deploy, and tick Force HTTPS by redirecting all HTTP traffic to HTTPS. Without it the plain-HTTP endpoint stays open beside the https one. OBSIDIAN_TC_PUBLIC_URL is https://<app name>.<root domain>, so the app must answer on that name.
  3. CapRover’s compose subset has no file mount, so the config file is baked into an image layer that CapRover builds on your host. A config change means editing the dockerfileLines in the app and redeploying.

The image tag is the obsidian_tc_version variable, which defaults to latest. The JWT secret is the jwt_secret variable, which CapRover fills with 64 random hex characters.

Tokens and the bundled authorization server

Section titled “Tokens and the bundled authorization server”

The JWT templates let you mint client tokens with the same config and secret. Run obsidian-tc token mint on a machine that has the config and the secret, as described in Authentication. Keep the secret out of the config file.

For OAuth clients such as claude.ai, enable the bundled authorization server by hand. Add as under auth in the config file, with the public origin as its issuer:

"auth": {
"mode": "jwt",
"requireJti": true,
"as": { "enabled": true, "issuer": "https://vault.example.com" }
}

Set OBSIDIAN_TC_AS_SETUP_TOKEN to a random value on the platform, then claim the operator account at /oauth/setup and remove the variable. The steps are in Turning it on and First-run claim. The auth.as keys are listed in configuration.

The vault on the volume is the only copy the server reads. Copy your notes into /data/vault on the volume, for example with the platform’s file manager or a one-off container. To keep the vault in step with a Git repository, use the -git image and the variables in Git-backed vault. The -git image is not part of these templates.

The templates track :latest in this repository, for parity with compose.http.yml. Pin a release version (:<version>) before you submit a template to a platform’s catalogue. Each catalogue has its own rules, and none of these submissions has been made.

  • Coolify takes compose templates through its next branch. The file goes in templates/compose/obsidian-tc.yaml, with a logo at svgs/obsidian-tc.svg, plus a docs PR. Coolify requires a minimum number of GitHub stars before it accepts a template, and this project does not meet it yet.
  • Dokploy takes a blueprint folder, blueprints/obsidian-tc/, that holds the compose file, the template, meta.json and a logo. meta.json version must equal the pinned image tag.
  • CapRover takes a definition at public/v4/apps/obsidian-tc.yml with a logo in the logos folder. A third-party repository can host the definition instead.
  • Not verified on a live platform. The templates were checked against the upstream formats and against compose.http.yml by the drift test, not by deploying them. A deploy on a Coolify v4 instance is still a manual check, as are the Dokploy import path and CapRover’s handling of $$cap_ variables inside a folded dockerfileLines entry.
  • The templates track :latest. Pin a version before submitting upstream.
  • No logo is shipped. Catalogue submissions need one.
  • No home-server stores. Umbrel, CasaOS and Unraid are covered by Home server, not here.