Using an external database with Takuto
By default, Takuto stores its data in a local SQLite file (takuto.db in the data
directory). For multi-user setups or production use, you can point Takuto at an
external PostgreSQL, MariaDB, or MySQL database instead.
Wire an existing database container with takuto-cli
If you use the Takuto CLI and your database runs in a container on this machine,
you don’t translate anything by hand. During takuto setup, choose “A database container
running on this machine (auto-wire it)” and give the URL exactly as you’d use it from your
host — the localhost one you’d paste into psql or a GUI client:
[database]
connection = "postgres://takuto:<DB_PASSWORD>@localhost:5433/takuto"
local_container = true # set by the wizard — tells the CLI to auto-wire on start
On every takuto start, the CLI then (1) finds the running container that publishes that
port and its internal port, (2) attaches it to Takuto’s network under the stable alias
takuto_db, and (3) writes a container-facing TAKUTO_DATABASE_CONNECTION
(…@takuto_db:5432/…) into takuto.env for the engine to use at boot. You keep thinking in
localhost terms; the CLI does the network-namespace translation.
The one requirement: start your database container before takuto start. If nothing
publishes that port yet, the CLI warns and leaves the string unchanged — bring the database up
and re-run takuto start.
A remote or host-native database is different: pick the remote / host-native option in the wizard and give a URL that is already reachable as-is.
The bundled Compose overlay
If you run the engine from Takuto Core with Docker Compose, the supported path is the bundled database overlay. It adds the database on the right network, injects the connection string for you, and waits for the database to be healthy before Takuto starts — which sidesteps both the networking and the startup-race problems below:
# PostgreSQL
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d
# or: make start BACKEND=postgres
# MariaDB / MySQL
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -d
# or: make start BACKEND=mariadb
Override the defaults with POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB (or the
MARIADB_* equivalents) in your environment. The overlay sets TAKUTO_DATABASE_CONNECTION
(which overrides [database].connection), so you don’t hand-edit any string, and a one-time
SQLite → external import runs on first boot.
Doing it by hand (running the engine directly)
If you run Takuto Core yourself without the CLI, there is no auto-wiring: you set a connection string the Takuto container can reach as-is. The rest of this page covers that path — a database you run separately, or one that already lives on another network.
The one rule that matters most
The connection string in config.toml is used by the Takuto container itself. So
the host in that string must be reachable from inside that container:
- ✅ If your database runs as another container, use its container/service name and its internal port, and make sure both containers share a network.
- ❌ Do not use
localhostor127.0.0.1— inside the Takuto container that points back at Takuto, not your database. - ❌ Do not use the host-published port (e.g. a
-p 5433:5432mapping). That port only exists on your host machine, not on the container network.
Rule of thumb:
localhost:<published-port>is for connecting from your host machine (psql, a GUI client).<container-name>:<internal-port>is for Takuto.
Reuse a database container you already have
Already running PostgreSQL (or MariaDB/MySQL) in a container — perhaps from another
Compose project? Don’t recreate it. Put it on the network the takuto container actually
uses — the dind sidecar’s — give it a stable alias, then build the string from that
alias:
# 1. Find the network the dind sidecar (and therefore takuto) is on:
docker inspect takuto-dind | grep -A8 '"Networks"'
# 2. Attach your existing DB container to that network under a stable alias:
docker network connect --alias takuto_db <network-from-step-1> <your-db-container>
# 3. Build the string from the alias + the database's INTERNAL port, e.g.:
# postgresql://<user>:<password>@takuto_db:5432/<db>
The host is the alias (takuto_db) — never localhost — and the port is the
container’s internal port (5432 / 3306), not a published -p port. Put it in
[database].connection (Step 2) and restart. The steps below cover creating a database
from scratch instead.
Step 1 — Run a database container on Takuto‘s network
Takuto‘s containers share a Docker/Podman network (when started via Compose, it’s the
project’s default network, e.g. <project>_default). Put your database on that same
network. The simplest approach is to add the database as a service in the same Compose
file so it joins automatically; alternatively, run it standalone and attach it to the
network.
Each engine needs a database and an application user created at first boot:
PostgreSQL
docker run -d --name takuto_db --network <takuto-network> \
-e POSTGRES_USER=takuto \
-e POSTGRES_PASSWORD='<DB_PASSWORD>' \
-e POSTGRES_DB=takuto \
-v takuto_db-data:/var/lib/postgresql/data \
-p 5433:5432 \
postgres:16-alpine
MariaDB
docker run -d --name takuto_db --network <takuto-network> \
-e MARIADB_ROOT_PASSWORD='<ROOT_PASSWORD>' \
-e MARIADB_DATABASE=takuto \
-e MARIADB_USER=takuto \
-e MARIADB_PASSWORD='<DB_PASSWORD>' \
-v takuto_db-data:/var/lib/mysql \
-p 3306:3306 \
mariadb:11
MySQL
docker run -d --name takuto_db --network <takuto-network> \
-e MYSQL_ROOT_PASSWORD='<ROOT_PASSWORD>' \
-e MYSQL_DATABASE=takuto \
-e MYSQL_USER=takuto \
-e MYSQL_PASSWORD='<DB_PASSWORD>' \
-v takuto_db-data:/var/lib/mysql \
-p 3306:3306 \
mysql:8
Persistence: the
-v takuto_db-data:/…named volume above is what makes your data survive container recreation. Without it, removing the container deletes the data.
Which network is “Takuto‘s network”? This is the part that trips people up. When the Docker-in-Docker sidecar is enabled, the
takutocontainer has no network of its own — it shares thedindcontainer’s network namespace (network_mode: service:dind). So the network that matters is the one thedindcontainer is attached to (typically the Compose project default, e.g.takuto-core_default), not a network named after Takuto. Find it with:docker inspect <dind-container> | grep -A8 '"Networks"'Attach your database to that network, and reference it by its name/alias on it.
Database already running on another network? You don’t have to recreate it — bridge it onto Takuto’s network and give it the alias your connection string expects:
docker network connect --alias takuto_db <takuto-network> <your-db-container>The
--alias takuto_dbmakes the database resolve astakuto_dbon that network, so a connection string like…@takuto_db:5432/…keeps working unchanged.
Step 2 — Set the connection string in config.toml
Edit the [database] section of .takuto/config.toml:
[database]
connection = "<CONNECTION_STRING>"
fail_fast = true # abort startup if the DB is unreachable (recommended)
import_from_sqlite = true # migrate existing local SQLite data on first connect
Use the scheme and internal port for your engine, with the database container name as the host:
| Engine | Connection string |
|---|---|
| PostgreSQL | postgresql://takuto:<DB_PASSWORD>@takuto_db:5432/takuto |
| MariaDB | mysql://takuto:<DB_PASSWORD>@takuto_db:3306/takuto |
| MySQL | mysql://takuto:<DB_PASSWORD>@takuto_db:3306/takuto |
MariaDB and MySQL both use the mysql:// scheme. Note the internal ports (5432 /
3306) — not the published ones from -p.
Step 3 — Restart Takuto
Takuto reads config.toml at startup, so restart it to apply the change:
takuto restart
Step 4 — Verify
Check the Takuto logs. A successful connection logs something like:
Multi-user database initialized (external backend) backend="postgres" url="postgresql://takuto:****@takuto_db:5432/takuto"
With fail_fast = true, a misconfigured or unreachable database makes startup abort
with an error rather than silently falling back to SQLite — so a clean boot means the
external database is in use. Takuto‘s egress layer also logs Allowing database sidecar: takuto_db:5432 when it recognizes the host. You can also connect to the database
and confirm Takuto‘s tables (users, sessions, credentials, repositories, …)
were created.
Connecting from your host machine (optional)
To inspect the database with a local client, use the published port (-p) and connect
via localhost:
postgresql://takuto:<DB_PASSWORD>@localhost:5433/takuto
mysql://takuto:<DB_PASSWORD>@localhost:3306/takuto
This is the one place localhost is correct — because you’re connecting from the host, not
from inside the Takuto container.
Troubleshooting
| Symptom | Likely cause |
|---|---|
pool timed out / connection refused at startup | String uses localhost or the published port instead of <container-name>:<internal-port>; or the DB container isn’t on Takuto‘s network. |
Database backend unreachable at startup | Two common causes: (1) the database wasn’t accepting connections yet when Takuto‘s 5 s SELECT 1 probe ran — a startup race; or (2) the DB isn’t on Takuto‘s network. Fixes: use the bundled overlay (it waits for the DB’s health check), make sure the DB is up and healthy before Takuto starts, or attach the DB to the dind container’s network (see Step 1). |
| Startup aborts immediately | fail_fast = true and the DB is unreachable or credentials are wrong — fix the string/credentials. (With fail_fast = false it logs a warning and falls back to SQLite instead.) |
| Data disappeared after recreating the DB container | No named volume was attached; add one (Step 1). |
| Auth / access errors | The app user/database wasn’t created, or the password in the string doesn’t match the container’s env vars. |
See the [database] reference for the full set of keys
(pool sizing, timeouts, SQLite import).