Deployment and hosting

Atamaia in production is a .NET 10 API, a React SPA, an Astro landing page, and PostgreSQL with pgvector. This page is the running topology.

Canonical API: https://api.atamaia.ai. Local setup: Getting started. Layer map: Architecture.

What is live

Internet
  |
Cloudflare
  |
  atamaia.ai           landing site
  app.atamaia.ai       SPA
  api.atamaia.ai       API only
  mcp.atamaia.ai       API only
  docs.atamaia.ai      redirect to https://atamaia.ai/docs
  support.atamaia.ai   redirect to https://atamaia.ai/support
  |
Caddy on the public VM
  |
systemd unit atamaia on 127.0.0.1:5000
  |
PostgreSQL + pgvector

app.atamaia.ai serves the SPA. Clients should use api.atamaia.ai for REST and MCP. Unauthenticated GET /mcp on the API host returns 401. X-Correlation-Id is set on /health. See Logging.

How a release ships

deploy-prod.sh runs on the dev box. It does not build on prod.

  1. Optional tests.
  2. Publish Atamaia.Server into a staging directory, not the live folder.
  3. Generate an idempotent EF SQL script.
  4. Build the landing site and SPA unless skipped.
  5. Optionally rebuild the code graph and sync those tables. Graph rows are a derived index, the only data allowed to flow dev to prod.
  6. Dump prod before applying the script.
  7. Apply migrate.sql.
  8. Rsync binaries to /opt/atamaia/publish.
  9. Restart the atamaia unit.
  10. Smoke-test /health.

Schema flows dev to prod. Application data never does. Prod is canonical for identity, memory, facts, chat, and agent runs. The script will not restore dev data onto prod.

Prod has the .NET runtime only. No SDK, no source tree, no dotnet-ef. Migrations travel as SQL. The pre-migration dump is left in place. The script prints the restore command and does not auto-restore.

Edge

TLS is DNS-01 via Cloudflare. The deployed config is deploy/Caddyfile.prod.

index.html is Cache-Control: no-cache, so a cached index cannot point at a missing bundle. /assets/* 404s instead of falling through to index.html. Brand aliases redirect to https://atamaia.ai.

Configuration

Secrets live in /etc/atamaia/atamaia.env, mode 600. They do not live in committed JSON. A double underscore maps to a colon: Encryption__Key is Encryption:Key.

Variable Role
ConnectionStrings__Atamaia PostgreSQL
Encryption__Key AES-256 root key, base64, 32 bytes
Jwt__Key HMAC signing key
Jwt__Issuer, Jwt__Audience Token validation
CF_API_TOKEN Caddy DNS-01

Do not use ConnectionStrings__DefaultConnection, Jwt__SecretKey, or Encryption__MasterKey. A known-compromised encryption key is refused at startup. Do not put a live key in appsettings.json.

Kestrel listens on port 5000. Caddy binds 443. There is no Redis connection string in the running app. Sessions are not stored in Redis.

Backups

Unit Schedule Notes
atamaia-backup.timer daily 02:30 age-encrypted dumps, 30-day retention, at least 7 kept
atamaia-backup-verify.timer Sunday 03:30 decrypts and restores; the writer cannot decrypt
[email protected] on failure writes a Critical log in the Data domain

A successful backup and a restorable backup are different facts. A Critical row is not an alert until a log subscription matches that domain and level. Hydration systemHealth is empty when nothing is subscribed. See Logging.

Dumps go to the encrypted data volume. Do not write a plaintext dump to the root disk.

Web search uses a SearXNG sidecar (deploy/searxng/). That is not the API. See Web search.

Checklist

  • Cloudflare in front. Origin not advertised.
  • Encryption__Key and Jwt__Key set, not in git.
  • ConnectionStrings__Atamaia only in /etc/atamaia/atamaia.env.
  • PostgreSQL not reachable from the public internet.
  • CORS is the committed origin list, not *.
  • Backup verify has run. Failure alerts are wired.
  • A log subscription exists for every domain the alerter writes.

Last updated