Sign inSign up

softwaretailor/aiserver

By softwaretailor

•Updated 1 day ago

AI Server serves chat, embeddings, image generation, speech and vision from hardware you own

Image
Networking
Machine learning & AI
Web servers
0

339

softwaretailor/aiserver repository overview

⁠AI Server in a container (Docker / Kubernetes / ECS)

Runs the aisuite-server daemon (net10.0 head) as a cloud container. Everything is configured by environment (12-factor); the image sits behind a load balancer / ingress / the AI Gateway and drains cleanly on rolling upgrades. Background + rationale: docs/v2/architecture/ai-server-deployment-forward-compat.md.

⁠Build & run

# Build from the REPO ROOT (the daemon's project graph spans shared v2/*). Needs BuildKit (Docker 23+).
docker build -f deploy/docker/aiserver/Dockerfile -t aisuite-server:local .

# Minimal run. NOTE: 0.0.0.0 serving requires a Pro entitlement + ≥1 API key (see "Licensing" below),
# so mount a prepared data dir:
docker run --rm -p 8080:8080 -v aisuite-data:/data aisuite-server:local

⁠Environment variables

VarDefault (image)Meaning
AISUITE_URLShttp://0.0.0.0:8080Bind address(es). Non-loopback ⇒ Pro + API keys required (fail-closed). ASPNETCORE_URLS also honored.
AISUITE_DATA/dataState root (keys, registry, audit, licensing). Mount a volume.
AISUITE_ENGINEstubstub | real | gateway. real pulls multi-GB models on first chat — opt in deliberately.
AISUITE_BINDloopbackPolicy view of the bind (loopback/lan/all). A non-loopback AISUITE_URLS auto-promotes this.
AISUITE_TRUST_PROXY(unset)1 to trust X-Forwarded-For/Proto/Host. Set only behind a trusted ingress/LB.
AISUITE_DRAIN_SECONDS8On SIGTERM: seconds to hold with /readyz=503 so the LB deregisters before shutdown.
AISUITE_SHUTDOWN_SECONDS30Graceful-shutdown budget for in-flight requests (fit inside the orchestrator's grace period).
AISUITE_LOG_JSON1One JSON log object per line on stdout (for Fluent Bit / Loki / CloudWatch).
AISUITE_SERVICE_MODE(unset)1 for supervisor-friendly logging (rarely needed in a container).
XDG_DATA_HOME/dataDo not unset. See below — the daemon will not start as a non-root user without it.
⁠Why XDG_DATA_HOME is load-bearing (read before changing the user or data dir)

The daemon keeps a survival root that is intentionally independent of AISUITE_DATA, and on Linux it resolves from XDG_DATA_HOME (else $HOME/.local/share). This image runs as the non-root app user, whose ~/.local/share does not exist — and .NET's Unix GetFolderPath returns an empty string for a directory that doesn't exist. The daemon then builds a relative path and tries to create it under its working directory, which app cannot write:

Unhandled exception. System.UnauthorizedAccessException: Access to the path '/app/AISuite' is denied.

XDG_DATA_HOME=/data fixes that and keeps the survival root inside your mounted volume — without it the data would land in the container's ephemeral layer and be lost on docker rm. The directory it points at must exist (the image creates /data). The same applies outside Docker: running the daemon under a non-root Linux account with no ~/.local/share (systemd DynamicUser=, distroless, K8s runAsUser) needs XDG_DATA_HOME set to a writable, existing directory.

⁠Health / orchestration probes

Distinct endpoints, all anonymous and excluded from usage accounting:

  • GET /livez — liveness. 200 while the process runs, even while draining. Use it to decide whether to restart the container. Never gate routing on it.
  • GET /readyz — readiness. 200 when accepting traffic, 503 once draining. Use it to decide whether to route to the container. This is what makes rolling upgrades zero-downtime.
  • GET /api/health — legacy wire-compat probe (kept for the local subprocess backend); prefer /livez.
⁠Kubernetes
livenessProbe:
  httpGet: { path: /livez, port: 8080 }
  periodSeconds: 10
readinessProbe:
  httpGet: { path: /readyz, port: 8080 }
  periodSeconds: 5
# Give the drain hook time to flip /readyz→503 and let the LB deregister before SIGKILL.
terminationGracePeriodSeconds: 40   # ≥ AISUITE_DRAIN_SECONDS + AISUITE_SHUTDOWN_SECONDS

On kubectl rollout, the new pod only receives traffic once /readyz is 200; the old pod flips to 503 on SIGTERM, is pulled from the Service endpoints, drains in-flight requests, then exits — no dropped requests.

⁠docker-compose
services:
  aiserver:
    build: { context: ../../.., dockerfile: deploy/docker/aiserver/Dockerfile }
    ports: ["8080:8080"]
    volumes: ["aisuite-data:/data"]
    environment:
      AISUITE_ENGINE: "real"
      AISUITE_TRUST_PROXY: "1"   # only if a proxy terminates in front
    stop_grace_period: 40s
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/readyz"]
      interval: 10s
      timeout: 3s
      retries: 3
volumes: { aisuite-data: {} }

⁠Licensing & API keys (required for network serving)

Binding to 0.0.0.0 is network serving, which fail-closes unless BOTH are present under AISUITE_DATA:

  1. A Pro entitlement — <data>/licensing/server-entitlement.json (issued by the licensed desktop app or your enrollment flow).
  2. At least one API key — <data>/credentials/server-keys.json (only SHA-256 hashes are stored). Issue with the server CLI, then bake the resulting file into the mounted volume.

Without them the daemon logs the reason and exits non-zero (by design — never serve inference unauthenticated on a network bind). Loopback-only runs need neither, but a loopback bind isn't reachable across the container network, so it's only useful for in-container smoke tests.

⁠Behind the AI Gateway

Point the Gateway (or any reverse proxy) at these containers; set AISUITE_TRUST_PROXY=1 so per-client rate limiting sees the real client IP via X-Forwarded-For. The Gateway health-checks /readyz and routes around draining pods — the same contract shipped clients already speak (retry + idempotency headers), so no client change is needed.

Tag summary

Content type

Image

Digest

sha256:fd3389355…

Size

706.1 MB

Last updated

1 day ago

docker pull softwaretailor/aiserver