This folder is the source of truth for deploying LibreDB Studio on CapRover as a one-click app.
Tracking issue: libredb-studio#56
| File | Purpose |
|---|---|
libredb-studio.yml |
CapRover captainVersion: 4 template (Docker-Compose + caproverOneClickApp block). |
libredb-studio.png |
256×256 app logo used by the CapRover one-click UI. |
libredb-studio-autoconnect.yml |
The auto-connect variant: the same Studio app plus a -discovery app that reads the Docker socket so Studio connects itself to the databases on the server. |
libredb-studio-autoconnect.png |
The same logo, under the file name upstream requires for the second app. |
The first two files are submitted as a PR to
caprover/one-click-apps
(public/v4/apps/libredb-studio.yml + public/v4/logos/libredb-studio.png) —
the official listing, merged and live.
The auto-connect variant went upstream the same way, as public/v4/apps/libredb-studio-autoconnect.yml and public/v4/logos/libredb-studio-autoconnect.png (caprover/one-click-apps#1346), and is listed there too (see Auto-connect variant).
CapRover dashboard → Apps → One-Click Apps/Databases → search LibreDB Studio. The search lists two LibreDB entries: LibreDB Studio, and LibreDB Studio (auto-connect), which also connects Studio to the databases on the server. No third-party repo to add.
The LibreDB 3rd-party repo that served this app while the official submission was in review is retired.
Its address, https://libredb.org/caprover-one-click-apps, no longer serves a list, so a CapRover that still has it under 3rd party repositories gets no app from it.
The repo is archived as libredb/caprover-one-click-apps-legacy, and its old name now belongs to the catalog fork in Releasing to the official catalog.
CapRover dashboard → Apps → One-Click Apps/Databases → select
>> TEMPLATE << at the bottom of the dropdown → paste the contents of
libredb-studio.yml → Next.
- Runs
ghcr.io/libredb/libredb-studioon container HTTP port3000, defaulting to a pinned version. The template advises against:latestbut does not stop it:validRegexaccepts any tag. - Generates a strong
JWT_SECRETand admin/user passwords automatically and echoes the login credentials on the final install screen. - Persists saved connections & settings with SQLite on a CapRover
persistent volume (
$$cap_appname-data→/app/data), surviving restarts and redeploys. - Sets
TRUSTED_PROXY_HOPS=1, because CapRover's nginx is one proxy hop in front of the app. With the default of 0 the login rate limiter keys on the leftmostX-Forwarded-Forentry, which the client writes. - Exposes optional AI/LLM fields (Gemini, OpenAI, Ollama, custom) — leave blank to disable.
libredb-studio-autoconnect.yml deploys the same Studio app plus a second app, <app>-discovery, so that Studio connects itself to the databases this CapRover runs.
It needs LibreDB Studio 0.18.0 or later, the first release that ships the exporter (docker/discover.mjs) and reads SEED_DISCOVERY_PATH.
With an older tag the -discovery app cannot start, because its command names a file that image does not have.
CapRover upgrades each app on its own, so when you upgrade, deploy the same version to both apps, the Studio app first: a Studio older than the exporter can refuse its export file, and then it withdraws every discovered connection.
Pick an app name of at most 39 characters that starts with a letter: the second app is named <app>-discovery, CapRover refuses an app name of 50 characters or more, and the project the install groups both apps in takes the app name and must start with a letter.
CapRover checks each name only when it creates that app or project, one at a time, and removes nothing when a later step fails.
A name of 40 to 49 characters therefore stops the install at <app>-discovery with "App Name is not allowed" and leaves the project and a Studio app without discovery; delete both before you install again under a shorter name.
For the same reason the template has CapRover create the Studio app first, so an install that stops early never leaves the app that holds the Docker socket running on its own.
What it adds to the plain template:
- The
-discoverycompanion. It runs the same image withcommand: ['node', '/usr/local/lib/libredb-studio/discover.mjs']. A one-clickcommandreplaces the image entrypoint, so the exporter runs as root, which the Docker socket needs. Every 10 seconds it lists the services oncaptain-overlay-networkand writes their names, hosts, images and the values of ten allow-listed database environment keys to/app/discovery/services.json, mode 0600, owned by uid 1001. It has no port and is not exposed as a web app. - The Docker socket.
/var/run/docker.sockis mounted into the companion only. Access to it is equivalent to root access on the host, which is why this is a separate catalog entry andlibredb-studio.ymlnever mounts it. The exporter sends GET requests to two Docker Engine API paths only, but that is a property of its code, not of the socket. A GET-only socket proxy was considered and not adopted (#1543). It would close the write paths, butGET /servicesstill returns every app's environment, so which credentials leave the companion stays a property of the exporter's code either way. That gain does not justify a third app holding the socket, from a third-party image: a TCP proxy would be reachable from every app oncaptain-overlay-network, and the proxies that serve a Unix socket need auser:field a one-click template cannot set. - The shared volume.
$$cap_appname-discoveredis mounted at/app/discoveryin both apps, never under/app/data, becausedocker-entrypoint.shchowns the directory ofSTORAGE_SQLITE_PATHto uid 1001 when it starts as root. The exporter refuses to start when that directory is not owned by its own uid or is writable by group or others. - Studio settings.
SEED_DISCOVERY_PATHturns the discovery source on,SEED_CACHE_TTL_MS=5000re-reads the export at most every 5 seconds, and both built-in samples are off, so only the CapRover databases are listed. Discovered connections are listed for the admin login only. The standard login cannot read the export file through a DuckDB connection: a non-admin DuckDB handle opens with statement-level file access closed (section 3.16 ofdocs/providers/duckdb.md, control 3.17 indocs/SECURITY.md), and the export is JSON, which DuckDB refuses to open as a database. The admin login can read it, so treat that login as holding every discovered database's password. Still give the standard login only to someone you trust: it reaches each connection's own database as the connection's DB user, and a connection that names a database file on the server is not limited to a directory (section 14.3 ofdocs/providers/duckdb.md, issue #125). - Apps to skip.
The optional "Apps to skip" field becomes
DISCOVERY_EXCLUDEof the companion: comma-separated CapRover app names whose databases Studio must not connect to. The exporter writes only the names of those apps to the export, and Studio's discovery status lists each one as skipped with the reason "listed in Apps to skip".
Both apps must run on one node, and that node must be a swarm manager.
Named volumes are local to a node, and only a manager answers the Docker service listing.
On a single-server CapRover this always holds.
On a cluster, pin both apps to the manager node under App Configs.
Otherwise the discovery status on Studio's admin Overview page reports swarm_unavailable (the companion runs on a worker) or keeps waiting for an export file it cannot see (the two apps run on different nodes).
Which engines are recognised, how their credentials are mapped and what the admin status shows is described in Platform discovery (CapRover).
An existing libredb-studio app is not adopted automatically.
To add discovery by hand, the app must already run 0.18.0 or later.
Below, studio stands for its app name.
-
In the
studioapp, under App Configs, add a persistent directory with Path in App/app/discoveryand Labelstudio-discovered. Add the environment variablesSEED_DISCOVERY_PATH=/app/discovery/services.jsonandSEED_CACHE_TTL_MS=5000, andTRUSTED_PROXY_HOPS=1if it is not set yet. To list only the CapRover databases, as the template does, also addLIBREDB_EMBEDDED_SAMPLE=falseandSQLITE_EMBEDDED_SAMPLE=false; without them the two built-in sample connections stay listed. Click Save & Restart. -
Create an app named
studio-discoverywith Has Persistent Data checked. Ifstudio-discoverywould have 50 characters or more, any shorter name works: nothing reads this app's name. -
In
studio-discovery, under HTTP Settings, check Do not expose as web-app externally and click Save & Restart. -
In
studio-discovery, under App Configs:- add a persistent directory with Path in App
/app/discoveryand Labelstudio-discovered, the same label as in step 1; - add a persistent directory with Path in App
/var/run/docker.sock, choose Set specific host path, and enter/var/run/docker.sockas Path on Host; - add the environment variables
DISCOVERY_OUTPUT=/app/discovery/services.jsonandDISCOVERY_NETWORK=captain-overlay-network, plusDISCOVERY_EXCLUDEwith any app names to skip; - set Service Update Override to the YAML below, which is what the template's
commandturns into; - click Save & Restart.
TaskTemplate: ContainerSpec: Command: ['node', '/usr/local/lib/libredb-studio/discover.mjs']
Until step 5 deploys the Studio image, this app still runs CapRover's placeholder image, so its tasks fail with
exec: "node": executable file not found in $PATH; that is expected. - add a persistent directory with Path in App
-
In
studio-discovery, under Deployment, deploy the imageghcr.io/libredb/libredb-studio:<version>with the same<version>thestudioapp runs. -
Sign in to Studio as the admin and open the Overview page under Admin. The discovery status should list your databases within about 30 seconds. If it keeps waiting for an export file, the two apps do not share the volume: run
docker volume lson the node, check that exactly one volume ends instudio-discovered, and check that both apps run on that node.
Set these under the app's App Configs tab to extend the deployment:
- SSO / OIDC —
NEXT_PUBLIC_AUTH_PROVIDER=oidc,OIDC_ISSUER,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET,OIDC_ROLE_CLAIM,OIDC_ADMIN_ROLES. - PostgreSQL storage backend (multi-node) —
STORAGE_PROVIDER=postgres,STORAGE_POSTGRES_URL=....
The release bump keeps both templates on the release version.
bun run chart:bump moves libredb-studio.yml and libredb-studio-autoconnect.yml with package.json, in the two places each file carries the version: the defaultValue of $$cap_version and the example inside its description.
bun run chart:check, which runs in the required Lint, Typecheck and Build check, fails a commit whose templates do not match package.json, so a release tag always holds the templates for that release and this copy can no longer fall behind unnoticed, the gap #268 described.
libredb-studio-autoconnect.yml never goes below 0.18.0, the first release with the exporter.
tests/unit/caprover-template.test.ts checks the rest of both files: it fails when the plain-HTTP cookie override is missing or stops saying what it costs, when an em dash or a pictograph creeps back in, and when the auto-connect variant breaks its own rules (the socket only in the companion, the shared volume, the disclosure that opens the install text).
The catalog's maintainer keeps version bumps manual: "When someone sends a PR we know that version works" (caprover/one-click-apps#1334). So a workflow prepares the branch, and a member tests it and opens the pull request.
- After each stable release,
caprover-fork.ymlstages both templates and their logos, byte for byte from the release tag, onlibredb/caprover-one-click-appsas the branchlibredb-studio-<version>.docker-build-push.ymlstarts it once the release images passed their channel E2E; a prerelease is never staged, and its templates stay on the last stable version. It runstests/unit/caprover-template.test.tson the tag first, which encodes the catalog validator's rules, and never opens a pull request. It runs none of the catalog's own npm checks: that would execute another repository's code on the release tag, whose Actions cache later release runs restore. The catalog's CI runs them on the pull request, and a member can run them first in a clone of the catalog:npm ci && npm run validate_apps && npm run formatter. - A member installs both staged templates on a CapRover: Apps → One-Click Apps/Databases →
>> TEMPLATE <<, paste the raw file from the branch, and keep the default version. - The member opens the pull request from the link in the run's summary, ticks the catalog's checklist from that test, and says what was tested. The summary lists every file the branch changes, so a change beside the version, such as a new logo, goes into the pull request's text too.
caprover-official in distribution/channels.yaml decides what the workflow may do, under update.fork:
| Setting | Value | Effect |
|---|---|---|
mode |
update |
The fork must exist. Its default branch is fast-forwarded to the catalog's before the push; a fork carrying commits the catalog lacks stops the run with nothing written to it. |
mode |
create_or_update |
A missing fork is created in the libredb org first. |
push |
auto |
The release run pushes the branch. |
push |
manual |
The release run only validates. A member pushes by running CapRover Catalog Fork by hand on the release tag: Run workflow, then Use workflow from the tag. |
The push needs the CAPROVER_CATALOG_TOKEN secret, because the workflow's own token cannot write to another repository.
For mode: update a fine-grained personal access token is enough: resource owner libredb, repository access to libredb/caprover-one-click-apps only, and Contents read and write.
GitHub lists the sync call (merge-upstream) under Contents write, and every token can read public repositories such as the catalog.
mode: create_or_update also calls GitHub's fork endpoint, which GitHub lists under Administration write and Contents read, so that token needs access to the libredb org's repositories rather than to one fork.
Without the secret the workflow validates and pushes nothing, by hand or not.
It never pushes over a branch whose templates differ from the release's, for example after a fix made during review: delete the branch to stage it again. The staged commit is authored as the project owner, like the operator catalog submissions, and its message names the run and who started it. If a release's run did not stage the branch, run CapRover Catalog Fork by hand on the tag: a branch that already holds the same templates is left alone, so a second run is harmless.
A change between releases, such as a description fix, still goes upstream by hand: merge it here first, then send the same bytes in a pull request.
Compare against the live template before you do.
This folder leads and the catalog follows, but that order has been broken twice: caprover/one-click-apps#1315 bumped the catalog to 0.9.59 directly, and #1335 put the AUTH_COOKIE_SECURE override and other fixes straight into the catalog, none of which came back here until 0.17.0.
curl -s https://raw.githubusercontent.com/caprover/one-click-apps/master/public/v4/apps/libredb-studio.yml \
| diff -u libredb-studio.yml -