Prerequisites
- Shell access to the Compose host, or
kubectlagainst the namespace jqfor reading the JSON logs
Steps
Establish which layer is broken
Run these four in order and stop at the first that fails.Under Compose,
postgres_init, app_migrations, kratos-migrate, and
rclone-init are one-shot. Exited (0) is success. Any other code is the
failure, and the services that depend on them will not have started.Read the logs as structured data
Every Python service emits one JSON object per line, so filter on fields:Tracebacks are in the
exception field with newlines escaped, so a failure is
one line, not fifty. See observability for the full
field list.The stack will not start
Compose aborts before creating any container. A required variable is empty.
The
SANDBOX_ACTIVATION_AUTH_SECRET and SANDBOX_CLEANUP_AUTH_SECRET are declared
${VAR:?message}, so Compose refuses rather than starting an unauthenticated
sandbox path. Set both in .env, at least 32 bytes each.Port already allocated. Find the holder and stop it, or change the published
port.db container will not start after pulling a newer checkout.
postgres:18 stores data under /var/lib/postgresql/<version>/docker, so the
Compose file mounts ./data/postgres at /var/lib/postgresql, not the legacy
/var/lib/postgresql/data. A data directory laid out for the old mount point
will not start.The API container restarts in a loop. Read the first error, not the last:SECRET_MANAGER_ENCRYPTION_KEY environment variable must be set is the most
common. SecretManagerFactory validates its configuration at construction, so
the process exits at startup instead of failing later on the first secret read.Pods stay Pending.FailedScheduling naming a RuntimeClass means mcpManager.runtimeClass points
at one the cluster does not have. Unschedulable PVCs mean no default
StorageClass.The migration Job sits in Init:0/1. Its wait-for-db init container polls
nc -z <host> <port> with no timeout, printing Waiting for database.... Check
that global.database.host resolves from inside the namespace.The database or migrations are wrong
Confirm connectivity first, from the pod that fails:Then the schema state:Mismatched output means migrations have not fully applied. Migrations must run
from
/app/apps/api — alembic.ini uses a relative script_location, so
running from the repository root fails with a missing-script_location error.For relation already exists, Can't locate revision, and multiple heads, see
database and migrations.Nobody can log in
Authentication spans Kratos, the frontend, and the backend, and the failure is
usually a URL mismatch rather than a broken service.
ORY_SDK_URL is for server-side calls from the frontend container;
ORY_BROWSER_URL is where the browser goes. They are allowed to differ, and
must, when pods cannot resolve the public domain. See
networking.Tasks are created but never run
Task execution is a Temporal workflow, so check the worker and Temporal, not the
API.The worker and the API must agree on all three Temporal values —
If the workflow does not appear, the API never submitted it. If it appears and
fails, the history shows which activity and why.
WORKFLOW__TEMPORAL_SERVER_URL, WORKFLOW__TEMPORAL_NAMESPACE, and
WORKFLOW__TEMPORAL_TASK_QUEUE. A worker polling a different task queue than the
API submits to produces exactly this symptom, with no error on either side.Under Compose, temporal has a 120-second health-check start period and the
worker waits for it. First start is slow; that is not a fault.Open the Temporal UI to see whether the workflow was started at all, and where it
stopped:Tool calls or MCP servers fail
mcpManager.backend: kubernetes
and the gateway_api feature on, the manager creates HTTPRoutes against
mcpManager.gateway. Absent Gateway API CRDs or Gateway means nothing programs
them.Instances start and immediately go idle. With
mcpManager.serverless.enabled, MCP_IDLE_TIMEOUT reclaims uncalled instances.
This is normal: the next call starts the workload again through the demand
gateway. Check mcp_runtime_instances.state to tell a reclaimed instance
(dormant) from a broken one (failed, with last_error set).A call to a dormant instance times out. The gateway bounds a cold start with
MCP_GATEWAY_STARTUP_TIMEOUT (default 5m), which must exceed the readiness
probe’s first success — an instance that needs longer than the timeout can never
come up. Concurrent callers get a retry response rather than an error while one
start is in flight.An MCP server cannot reach its upstream. The instance egress NetworkPolicy
denies all cluster-internal and link-local ranges. An upstream inside the
cluster is blocked by design; add it to
mcpManager.instanceNetworkPolicy.extraEgress.File uploads or artifacts fail
Presigned URLs are the usual cause, and the error surfaces in the browser rather
than in any server log.Empty means presigned URLs point at the in-cluster object store address, which
the browser cannot resolve. Set
global.storage.publicEndpoint. A CORS
rejection after that means global.storage.cors.allowedOrigins was empty at
bootstrap, which skips applying a rule entirely.Verify
After any fix, confirm the whole path rather than the symptom:Troubleshooting
Failures that look like one problem and are another:Everything is healthy and every action is denied
Everything is healthy and every action is denied
OpenFGA has no authorization data — a fresh store after a restore, or a
bootstrap that did not run. The authorization reader fails closed, so a
working platform where nothing is permitted looks like a permissions bug
rather than a missing database.
helm upgrade succeeded and the platform runs the old version
helm upgrade succeeded and the platform runs the old version
The migration Job is a normal resource, not a Helm hook, so it does not gate
the rollout. Check the Job separately. See [upgrades](/self- host/upgrades)
.
Logs are plain text instead of JSON
Logs are plain text instead of JSON
Something called
logging.basicConfig after setup_logging , replacing the
handler that carries the formatter and the redaction filters. This is a
logging defect and a secret-leak risk, not a cosmetic one.A Prometheus scrape returns 404
A Prometheus scrape returns 404
Expected. No AgentArea service exposes
/metrics , despite the chart
rendering METRICS_ENABLED . See [observability](/self- host/observability)
.Provider icons are broken and OAuth callbacks fail
Provider icons are broken and OAuth callbacks fail
Both are served from
API_BASE_URL . When global.api.publicUrl is empty
the chart derives it from the backend ingress host, assuming https , and
falls back to a ClusterIP URL if ingress is off.Stored credentials fail with InvalidToken
Stored credentials fail with InvalidToken
SECRET_MANAGER_ENCRYPTION_KEY no longer matches the ciphertext in
encrypted_secrets . There is no recovery path without the original key.
See secrets backends .Related
Deploy with Docker Compose
Run the full AgentArea platform on one host with docker- compose.yaml
Deploy on Kubernetes with Helm
Install the agentarea Helm chart, decide which bundled dependencies to keep
Run database migrations
Apply, inspect, and recover Alembic migrations for the AgentArea schema, on
Kubernetes and on
Collect logs and traces
Read AgentArea’s structured JSON logs, enable OpenTelemetry tracing
Configure networking and ingress
Expose AgentArea over ingress, set the public URLs the platform advertises