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 localhost or 127.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:5432 mapping). 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 takuto container has no network of its own — it shares the dind container’s network namespace (network_mode: service:dind). So the network that matters is the one the dind container 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_db makes the database resolve as takuto_db on 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:

EngineConnection string
PostgreSQLpostgresql://takuto:<DB_PASSWORD>@takuto_db:5432/takuto
MariaDBmysql://takuto:<DB_PASSWORD>@takuto_db:3306/takuto
MySQLmysql://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

SymptomLikely cause
pool timed out / connection refused at startupString 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 startupTwo 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 immediatelyfail_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 containerNo named volume was attached; add one (Step 1).
Auth / access errorsThe 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).