00
Read Me Out Loud
For AI Assistants

AiMANAC Backend Setup —
paste this page into your AI assistant.

You probably landed here because you asked Claude or ChatGPT to help you deploy your AiMANAC backend, and it told you to read this page. It's right. This page exists so your AI assistant doesn't guess — every value it needs to write your nginx / docker-compose.yml lives below, machine-readable and dated.

What to do: copy the prompt in panel 01, paste it into your AI assistant along with this page's URL, and follow what it gives you. That's it.

Audience · Human Audience · AI Assistant Audience · Search Indexer
01
Copy This Prompt
Primary  ·  ~10 lines

Copy the text below and paste it into Claude, ChatGPT, or whichever AI you're using. It tells the model where to find ground-truth values, what to generate, and what to refuse to guess.

PROMPT  ·  plain text
I'm deploying the AiMANAC self-hosted backend on my own server. Help me write
the configs and walk me through it step by step.

Canonical setup data lives at:
  https://aimanac.llmtech.net/setup/llm/

Before you answer:
  1. Fetch that page (or read the JSON inside its <script id="aimanac-deploy-spec"> tag).
  2. Use ONLY the values from that spec — image tag, internal port, env vars, required services,
     build commands (spec key "build"), port mapping (spec key "port_mapping").
  3. If the spec's "released_at" is older than its "freshness_window_days", stop and tell me.
  4. Do NOT recall AiMANAC values from your training data. They will be wrong.
  5. Read the "anti_hallucination" array on that page and treat each item as a hard constraint.
  6. Confirm the Docker daemon is running BEFORE producing any docker build command
     (spec key "prereq.docker_daemon").

My environment:
  - OS:           {{ubuntu 24.04 / debian 12 / macos / other}}
  - Public host:  {{my-aimanac.example.com}}
  - TLS:          {{letsencrypt / cloudflare / other}}
  - I want:       {{docker-compose on a single VPS / kubernetes / bare metal}}
  - Build host arch: {{linux/amd64 / linux/arm64 / apple-silicon-mac-targeting-amd64}}

Now produce: (a) the two docker build commands for aimanac-server + mailsloth-server
(using the Dockerfile paths from spec.build.images), (b) my docker-compose.yml with
correct host:container port mapping per spec.port_mapping, (c) my nginx server block
including the /ihp-ws location, (d) the .env file, (e) the curl commands I should run
to verify. Show your sources by quoting the JSON keys you used.

If I am also deploying sibling apps (BROILR / Archimedes / FL33T / etc.), read
spec.sibling_deploys and pick option A (Railway per sibling), B (single VM co-hosted),
or C (Railway multi-service in one project) based on my preference. Per-sibling
authoritative env vars live in each sibling repo's railway.toml.

TIP   Most assistants will not actually fetch the URL unless you paste the page contents too. If yours refuses, copy the JSON from panel 02 into the chat as well.

02
Deploy Spec · Machine-Readable
JSON-LD  ·  #aimanac-deploy-spec

This is the canonical source of truth. The visible block below is the same JSON embedded in <script type="application/json" id="aimanac-deploy-spec"> for scrapers and indexers. Values in amber mustaches are filled in at release time.

SPEC  ·  application/ld+json

    
03
Build Order & Prerequisites
Pre-Deploy  ·  Captured From XZA's Reference Deploy

The deploy spec above tells your AI what the running stack looks like. This panel tells your AI how to get there. Every fact below was captured during an end-to-end deploy of aimanac-rs on the maintainer's reference host. Each item closed a customer-visible footgun on the way.

Step 0 · Docker daemon must be running. Before any docker command works, the daemon has to be up. On macOS that's open -a Docker (or open -a OrbStack); on Linux that's systemctl start docker. Wait until docker ps returns without Cannot connect to the Docker daemon. Skipping this is the most common first-fault we see — every docker build below assumes the daemon is live.

Step 1 · Pull the two server images from GHCR (customer path). Pin the tag from the JSON spec block at the bottom of this page (currently rust-cutover-20260520-ghcr-pipeline). No aimanac-rs git clone. After Everplay flips each package to Public on GitHub Packages, docker pull works without login.

SHELL  ·  pull (customer VPS / fleet host)
TAG=rust-cutover-20260521-satchel-migration-fix

docker pull ghcr.io/everplay-tech/aimanac-server:${TAG}
docker pull ghcr.io/everplay-tech/mailsloth-server:${TAG}

docker inspect ghcr.io/everplay-tech/aimanac-server:${TAG} --format '{{.Architecture}}'   # expect: amd64

Step 1b · Maintainer path — build from aimanac-rs (optional). Only when publishing a new tag or air-gapped. The aimanac-server Dockerfile is at the repo root; mailsloth-server uses infrastructure/docker/mailsloth-server.Dockerfile with repo-root context. See aimanac-rs/infrastructure/ghcr-publish/PUBLISH.md.

SHELL  ·  build (run from aimanac-rs/ root)
TAG=customer-cycle5-$(date +%Y%m%d)

# aimanac-server image (root Dockerfile)
docker build \
  --build-arg CARGO_FEATURES=backpack-ingest \
  --build-arg IMAGE_TAG=$TAG \
  -t aimanac-server:$TAG .

# mailsloth-server image (separate Dockerfile, repo-root build context)
docker build \
  -f infrastructure/docker/mailsloth-server.Dockerfile \
  --build-arg IMAGE_TAG=$TAG \
  -t mailsloth-server:$TAG .

Step 2 · Compose uses image:, not build:. Fleet and co-hosted YAML on this page reference ghcr.io/everplay-tech/<service>:${TAG}. Set TAG in your env file or export before docker compose pull && docker compose up -d.

Step 3 · Cross-architecture path (only if you must build on Apple Silicon for an x86 host). Mac builds default to linux/arm64; most cloud hosts (Hetzner / DigitalOcean / Linode general compute) are linux/amd64. A foreign-arch image loads on disk but fails at runtime with exec format error — no compile-time signal. If you must build on Mac, force the platform:

SHELL  ·  cross-arch build (Apple Silicon → x86 host)
docker buildx build --platform linux/amd64 --load \
  --build-arg CARGO_FEATURES=backpack-ingest \
  --build-arg IMAGE_TAG=$TAG \
  -t aimanac-server:$TAG .

docker buildx build --platform linux/amd64 --load \
  -f infrastructure/docker/mailsloth-server.Dockerfile \
  --build-arg IMAGE_TAG=$TAG \
  -t mailsloth-server:$TAG .

# Verify arch after build:
docker inspect aimanac-server:$TAG --format '{{.Architecture}}'   # expect: amd64
docker inspect mailsloth-server:$TAG --format '{{.Architecture}}' # expect: amd64

NOTE   QEMU-emulated Rust builds on Apple Silicon take ~30–60 min. Native amd64 build on the target host takes ~5–10 min. The native-target path is faster and avoids the arch trap. Prefer it.

Step 4 · Port mapping — host:container is 3002:3000, not 3002:3002. The aimanac-server binary listens on container-internal port 3000. If you front it with nginx terminating TLS at the public address, the nginx proxy_pass contract uses host port 3002. The correct docker run -p mapping is -p 127.0.0.1:3002:3000 (host:container). An inverted -p 3002:3002 mapping starts cleanly but yields HTTP 502 from nginx — silent until you curl /health. Verify with docker inspect <container> --format '{{.NetworkSettings.Ports}}'.

04
Sibling-Server Deploys
BROILR  ·  Archimedes  ·  FL33T  ·  ...

AiMANAC ships with sibling apps — each a $1 standalone iOS app (BROILR, Archimedes, FL33T, Bombadil, PSL, DeepDive, MailSloth, Satcheli, etc.) backed by its own Rust server binary in its own repo. The siblings reuse the AiMANAC backend rules (one Postgres, JWT secrets, IHP transport) and each has its own Railway-friendly Dockerfile + railway.toml. Per-sibling deploy hubs live at /apps/.

Three deploy options. The same image works in all three; pick the one that matches your cost / ops complexity preference.

A · A la carte (Railway, one project per sibling)

3 commands per sibling  ·  easiest scaling

One Railway project per sibling. Each project gets its own Postgres add-on, env vars, and public domain. Customer's bill scales linearly. Project name should match the binary / service name (broilr-server, fl33t-server) — not the repo name (FL33T-rs).

SHELL  ·  per sibling
cd broilr-rs/                              # or archimedes-server/, FL33T-rs/, etc.
railway init --name broilr-server          # creates new Railway project
railway up --detach                         # uploads source; Dockerfile build
railway status                              # confirms link
railway logs                                # tail build + runtime

B · Co-hosted (single VM, N docker containers)

docker-compose.yml on one Hetzner / VPS / Mac mini

One box, four (or more) docker processes. Customer pays for one VM. Each sibling binary stays a distinct process — preserves the family's loose-coupling design. Each sibling can share one Postgres (own schema) or have its own DB container. Standard docker-compose up -d.

YAML  ·  docker-compose.yml skeleton
# TAG=rust-cutover-20260521-satchel-migration-fix (aimanac; pin from JSON spec below)
services:
  aimanac-server:
    image: ghcr.io/everplay-tech/aimanac-server:${TAG}
    ports: ["127.0.0.1:3002:3000"]   # host:container — NOT 3002:3002
    environment:
      REDIS_URL: redis://redis:6379/0
    env_file: aimanac.env
    depends_on: [postgres, redis]
  mailsloth-server:
    image: ghcr.io/everplay-tech/mailsloth-server:${TAG}
    ports: ["127.0.0.1:3010:3010"]
    env_file: mailsloth.env
    depends_on: [postgres]
  broilr-server:
    image: ghcr.io/everplay-tech/broilr-server:${TAG}
    ports: ["127.0.0.1:3030:3010"]   # image listens on 3010 inside container
    env_file: broilr.env
    depends_on: [postgres]
  archimedes-server:
    image: ghcr.io/everplay-tech/archimedes-server:${TAG}
    ports: ["127.0.0.1:3020:3020"]
    env_file: archimedes.env
    depends_on: [postgres]
  fl33t-server:
    image: ghcr.io/everplay-tech/fl33t-server:${TAG}
    ports: ["127.0.0.1:3000:3000"]
    env_file: fl33t.env
    depends_on: [postgres]
  postgres:
    image: pgvector/pgvector:pg16
    # each sibling can use its own schema in one DB
  redis:
    image: redis:7-alpine

C · Railway single-project multi-service. One Railway project with N services (one per sibling). Saves a bit on project-overhead pricing; loses per-sibling project isolation. Use railway service add from inside one project and assign each sibling repo to its own service. Same Dockerfile builds; same env-var pattern.

What customers don't see in this story (yet).

NOTE   A single-binary consolidated backend is not on offer. The siblings are intentionally separate Rust binaries in separate workspaces; consolidating would require a multi-process container (Docker anti-pattern) or a merged binary refactor. Options A / B / C above are the supported shapes.

05
If Your AI Suggests These, Ignore It
Anti-Hallucination Facts

These are the failure modes we keep seeing customer LLMs invent. They usually come from outdated blog posts in the training data. None of them produce an error — your deploy will look fine and silently misbehave. Show this list to your assistant if it tries any of them.

  1. NO

    Skipping the /ihp-ws nginx location block

    The iOS client uses a WebSocket transport at /ihp-ws. Without an explicit location that forwards Upgrade + Connection headers, the app silently falls back to long-polling. No error, just mystery latency.

  2. NO

    Leaving proxy_buffering on for /ihp-ws

    Buffering stalls WebSocket frames mid-flight. The connection appears up, but messages arrive in clumps. Set proxy_buffering off on that route.

  3. NO

    Using HTTP/1.0 for proxy_pass

    WebSocket upgrades require proxy_http_version 1.1;. Without it, the 101 handshake never completes and nginx returns 502 — or worse, the client falls back transparently.

  4. NO

    Substituting postgres:16 for pgvector/pgvector:pg16

    AiMANAC needs the vector extension at boot. Vanilla Postgres will start, accept connections, then crash on first index build. Use the pgvector image.

  5. NO

    Recalling the Docker image tag from memory

    Image tags are bumped weekly. The only safe source is the image.tag field in panel 02. If your assistant types a tag without quoting that field, reject the output.

  6. NO

    Forgetting App Groups on the iOS side

    The iOS app needs an App Group entitlement matching app_groups.id in the spec for the share extension to read the backend URL. Missing it = blank pasteboard, no error.

  7. NO

    Running docker build without starting the daemon first

    Customer LLMs often skip the prerequisite. docker version must return both Client and Server lines before any build will work. On macOS that's open -a Docker (or open -a OrbStack); on Linux systemctl start docker. Cannot connect to the Docker daemon = daemon isn't up.

  8. NO

    Looking for mailsloth-server.Dockerfile at the repo root

    Customer-facing tip: it lives at infrastructure/docker/mailsloth-server.Dockerfile, not at the repo root and not under crates/mailsloth-server/. Build context is still the repo root, so invoke docker build -f infrastructure/docker/mailsloth-server.Dockerfile ... . from the repo root.

  9. NO

    Building on Apple Silicon without --platform linux/amd64

    Docker Desktop on M-series Macs builds linux/arm64 by default. Foreign-arch images load on amd64 cloud hosts (Hetzner / DigitalOcean / Linode) and then crash at runtime with exec format error — no compile-time signal. Either build on the target host natively (recommended) or pass --platform linux/amd64 --load to docker buildx.

  10. NO

    Mapping host:container as 3002:3002 instead of 3002:3000

    aimanac-server listens on container-internal port 3000. With nginx fronting it, the public-facing host port is 3002. The correct mapping is -p 127.0.0.1:3002:3000. Inverting it (:3002:3002) starts the container cleanly but returns HTTP 502 from nginx — looks like a healthy container, fails every request.

06
Reference Assets
Copy / Paste / Download

Two starter files. Replace every {{MUSTACHE}} with the corresponding value from panel 02 — don't let your AI fill them from memory.

nginx · /ihp-ws block

examples/nginx-ihp-ws-block.conf  ·  ~25 lines

NGINX-CONFIG

        

docker-compose · full stack

examples/docker-compose-example.yaml  ·  ~55 lines

YAML

        
07
Verify The Deploy
curl  ·  Smoke Tests

Run these against your deploy. Replace your-host with the public hostname you set in AIMANAC_PUBLIC_HOST. Both should pass — if the second one returns anything other than 101, your /ihp-ws route is wrong even if the first one is green.

A · Health Check
SHELL
curl -sS https://your-host/health
EXPECT  ·  HTTP 200  ·  body {"status":"ok","version":"…"}
B · WebSocket Upgrade Handshake
SHELL
curl -i \
  -H "Host: your-host" \
  -H "Upgrade: websocket" \
  -H "Connection: upgrade" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  https://your-host/ihp-ws
EXPECT  ·  HTTP 101 Switching Protocols  ·  headers include Upgrade: websocket