Run database migrations
AgentArea’s schema is managed by Alembic and applied by theagentarea-api migrate command. Both deployment targets run it automatically
before the API starts, so most of the time there is nothing to do. This guide
covers the cases where there is: inspecting what ran, applying migrations by
hand, and recovering a database that Alembic will not advance.
Migrations run from agentarea-platform/apps/api, never from the repository
root. alembic.ini sets script_location = alembic as a relative path, so
Alembic resolves the migration directory against the working directory. Both the
Kubernetes Job and the Compose service set working_dir: /app/apps/api for this
reason.
Prerequisites
- A reachable PostgreSQL instance with the
agentareadatabase created - For local work: Python 3.12 or later and
uv, from a clone of the repository - The same
DATABASE_URLorPOSTGRES_*values the platform uses
Steps
1. Understand what runs automatically
Both are ordered ahead of the API. Compose declares
app_migrations: condition: service_completed_successfully as a dependency of
app, so a failed migration keeps the API from starting rather than letting it
serve against a stale schema. On Kubernetes the Job has
ttlSecondsAfterFinished: 300 and disappears five minutes after it succeeds.
agentarea-api migrate does more than alembic upgrade head:
- It opens a connection and runs
SELECT 1, printingDatabase connection successful. - It reads the current Alembic revision.
- If there is no revision and the
provider_specstable exists, it treats the schema as already current and runsalembic stamp headinstead of replaying migrations. This is the path for a database created by an older bootstrap. - If there is no revision and no
provider_specstable, it runsalembic upgrade headfrom empty. - If there is a revision, it runs
alembic upgrade headnormally.
Migration failed: and exits 1.
2. Apply migrations by hand
When you need to run them outside the normal startup path — after restoring a backup, or against an external database the Job cannot reach. On Kubernetes, run a one-off pod from the API image:3. Inspect migration state
Fromagentarea-platform/apps/api:
4. Create a migration
Only when changing the schema in code.alembic.ini sets
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(slug)s,
so new files are named by ISO-style timestamp — 20260729_1432_add_widget_table.py.
Do not rename them into any other scheme.
Always read the generated file before committing. Autogenerate does not detect
every change, and it will happily drop a column it does not recognise.
5. Know which databases exist
One PostgreSQL instance holds several logical databases. Alembic owns only the first.
Under Compose these are created by
postgres_init, which is idempotent. On
Kubernetes each has its own create-*-db-job.
Verify
Confirm the database is at the revision the code expects — the two commands must print the same identifier:Migrations completed successfully, or Stamped database to head revision when the schema pre-existed. On Kubernetes:
1/1.
Confirm the schema is actually usable, not merely stamped:
Troubleshooting
The migration Job sits inInit:0/1 and logs Waiting for database.... Its
wait-for-db init container polls nc -z <host> <port> and never times out.
The host is global.database.host, or the bundled PostgreSQL service when that
is empty. Verify the host resolves from inside the namespace and the port is
reachable.
Migration failed: with a relation already exists error. Alembic is
replaying a migration against a schema that already has the object. This happens
when a database has tables but no alembic_version row, and the auto-stamp path
did not trigger because provider_specs was absent — a “dirty” database, in the
command’s own words. Confirm the schema really is current, then stamp it:
Can't locate revision identified by '<hash>'. The database records a
revision that does not exist in the image’s migration directory — usually a
downgrade to an older image after a newer one migrated, or a branch whose
migration was never merged. Roll forward to the image that contains the
revision. Do not delete the alembic_version row to make the error go away; it
strands the schema at an unknown point.
alembic branches returns rows. Two migrations claim the same parent,
typically from two branches merged without rebasing. Generate a merge revision:
FAILED: No 'script_location' key found. The command ran from the repository
root. alembic.ini uses a relative script_location, so the working directory
must be agentarea-platform/apps/api (/app/apps/api in the image).
A helm upgrade leaves the API running an older schema. The migration Job
is a normal resource, not a Helm hook — the chart notes this is deliberate, to
avoid a dependency deadlock with the database. It therefore does not block the
Deployment rollout. Check the Job completed before assuming the upgrade is done.
See upgrades.