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.
What every template sets
Section titled “What every template sets”| 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.
HTTPS is required
Section titled “HTTPS is required”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.
Coolify
Section titled “Coolify”- In Coolify, create a new resource from a Docker Compose source and paste the contents of
deploy/paas/coolify/obsidian-tc.yaml. - Set the domain on the
obsidian-tcservice. ItsSERVICE_URL_OBSIDIAN_TC_8765variable routes port 8765 through that domain. - Deploy. Coolify generates
SERVICE_PASSWORD_64_OBSIDIANTC, which is the JWT secret, and writes the config file from thecontent: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.
Dokploy
Section titled “Dokploy”- Create a Compose service and import the template. The files are
deploy/paas/dokploy/docker-compose.yml,template.tomlandmeta.json. If your Dokploy version does not import a template folder, pastedocker-compose.ymlinto the service and copy the values fromtemplate.tomlby hand: theOBSIDIAN_TC_PUBLIC_URLandOBSIDIAN_TC_JWT_SECRETentries, theobsidian-tcdomain on port 8765, and the config file. - Dokploy generates the domain and a 64-character password. The config file is mounted from
../files/obsidian-tc.config.json, which the template writes. - 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_URLis anhttps://address. - Deploy, then check that the service answers on its https domain.
- To use your own domain instead, point the domain’s host at it, set
OBSIDIAN_TC_PUBLIC_URLtohttps://<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 totransports.http.allowedHostsin the config file.
CapRover
Section titled “CapRover”- Add
deploy/paas/caprover/obsidian-tc.ymlas a one-click app. The file is a CapRover template, so use the template entry for pasting a YAML definition. - 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_URLishttps://<app name>.<root domain>, so the app must answer on that name. - 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
dockerfileLinesin 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.
Vault source
Section titled “Vault source”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.
Submitting upstream
Section titled “Submitting upstream”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
nextbranch. The file goes intemplates/compose/obsidian-tc.yaml, with a logo atsvgs/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.jsonand a logo.meta.jsonversionmust equal the pinned image tag. - CapRover takes a definition at
public/v4/apps/obsidian-tc.ymlwith a logo in the logos folder. A third-party repository can host the definition instead.
What this does not do yet
Section titled “What this does not do yet”- Not verified on a live platform. The templates were checked against the upstream formats and
against
compose.http.ymlby 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 foldeddockerfileLinesentry. - 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.