Skip to main content

Deploy locally or on a VM

Run the Anyray stack with docker compose directly on the host.

Prerequisites​

  • Docker Engine (v24+) and Docker Compose v2: docker compose version
  • ~2 GB RAM free
  • Ports 3000 (console) and 8787 (gateway API) open to your org network or VPN CIDR only, never 0.0.0.0/0
  • git and openssl on PATH
  • Anyray deployment token (adt_…)

Install​

1
Run setup
git clone https://github.com/anyrayHQ/install anyray && cd anyray
./setup.sh --connect <adt_token>
docker compose up -d

setup.sh generates every secret locally into .env, and --connect wires metering to the Billing App. docker compose up -d pulls the published images and starts four services:

ServiceExposed portWhat it is
gateway:8787OpenAI-compatible multi-provider API
proxy (console):3000Admin console (Spend, Traces, Optimizer, Privacy)
optimizerinternalRequest and response optimization hook
postgresinternalSpend and trace store
2
Verify
docker compose ps # all services running/healthy
curl -fs http://localhost:8787/ && echo "gateway ok" # gateway answers

All four services read running, with gateway, optimizer, and postgres (healthy).

3
Open the console

Open http://<your-host>:3000 and sign in with the admin key setup.sh printed. It is also in .env as ANYRAY_ADMIN_TOKEN.

To finish setup from your coding agent instead of the console, continue with Run Anyray from your AI agent.

Traces live in Postgres (anyray_traces and anyray_observations, auto-created) and the console reads them in-process. There is no separate trace datastore to run.

Upgrade​

git fetch && git reset --keep origin/main && docker compose pull && docker compose up -d --remove-orphans

Run it in the install checkout. The git step refreshes the compose files and keeps your .env, which is untracked, so --remove-orphans can drop a service a release retires.

The gateway triggers the bundled updater itself, so soft updates apply on their own. Run the command above for hard updates, or after opting out (Configure → Updates). The console's Updates panel says which kind a release is. The stack tracks the moving stable channel. Pin ANYRAY_IMAGE_TAG=vX.Y.Z in .env to hold a version.

Configuration​

Set these in .env, then run docker compose up -d again.

.env variableWhat it controls
ANYRAY_HSTSEmits HSTS headers. Set only when serving over HTTPS.
ANYRAY_ALLOW_PLAINTEXTDeploy gate for plaintext content capture. The content mode itself is set in the console.
ANYRAY_RATE_LIMIT_RPMPer-identity /v1 request limit per minute (e.g. 600).
ANYRAY_RATE_LIMIT_IP_RPMPer-source-IP /v1 request limit per minute (e.g. 1200).
ANYRAY_RATE_LIMIT_UNAUTH_RPMUnauthenticated endpoint request limit per minute (e.g. 60).
ANYRAY_MAX_CONCURRENT_REQUESTSMax simultaneous /v1 requests per identity or IP (e.g. 20).
ANYRAY_MAX_BODY_BYTESMax request body size in bytes (e.g. 33554432).
HTTPS_PROXYCorporate forward proxy for the gateway's outbound calls (e.g. http://proxy.example.com:3128). See Behind a forward proxy.
HTTP_PROXYThe same, for plain http:// upstreams. Leave unset unless you route to one.
NO_PROXYYour own internal hosts the gateway reaches without the proxy (e.g. .corp.example.com).

For a public HTTPS gateway, the stack ships a Caddy edge behind the public profile that provisions and renews a Let's Encrypt certificate, with the console and /admin staying in-network: Gateway TLS.

Managed Postgres

Set ANYRAY_SPEND_DB_URL to use a managed instance instead of the bundled Postgres. The spend store uses it directly and the trace store falls back to it, so one managed database serves both. Set ANYRAY_OBSERVABILITY_DB_URL as well only to split traces onto a separate database.

.env
ANYRAY_SPEND_DB_URL=postgresql://user:password@db.example.com:5432/postgres

The gateway auto-creates the anyray_spend, anyray_traces, and anyray_observations tables on first use. Trace content follows the content mode: AES-256-GCM ciphertext by default, omitted in off, and readable only in deploy-gated plaintext.

Amazon RDS without a stored password​

An RDS endpoint carrying a user and no password uses IAM database authentication. The gateway mints a short-lived token from the machine's own AWS identity on every connection, so .env holds no database credential.

.env
ANYRAY_SPEND_DB_URL=postgresql://appuser@mydb.abc123.us-east-1.rds.amazonaws.com:5432/anyray

Three things must be true on the AWS side. IAM auth is enabled on the instance, the database user was created with GRANT rds_iam TO appuser, and the VM's instance profile grants rds-db:connect on arn:aws:rds-db:<region>:<account>:dbuser:<db-resource-id>/appuser. TLS is configured and verified for you against Amazon's RDS roots, so no sslmode and no CA file. The Kubernetes reference walks the same three steps in more detail.

Containers need an extra IMDS hop. Instance-profile credentials come from the instance metadata service, and EC2 defaults the metadata hop limit to 1, which the Docker bridge network consumes. Raise it to 2, or every connection fails to authenticate:

aws ec2 modify-instance-metadata-options \
--instance-id <instance-id> \
--http-put-response-hop-limit 2 \
--http-endpoint enabled

Confirm the resolved mode with GET /admin/health. .spend.auth reads rds-iam when tokens are being minted, and password when something still supplies one. A password in the URL, a ?password= parameter, or PGPASSWORD in the environment all keep password auth, so an existing deployment cannot switch modes by accident.

System requirements​

The real footprint is about 1 vCPU and 1.5 GB, so a small host is plenty. Size up only for high trace or spend volume, because it is Postgres that grows.

TiervCPU / RAMAWSGCPAzureOCI
Recommended2 / 4 GBt3.mediume2-mediumStandard_B2msE4.Flex 1 OCPU / 4 GB
Minimum / demo2 / 2 GBt3.smalle2-smallStandard_B2sE4.Flex 1 OCPU / 2 GB

Disk is what grows. The gateway keeps 90 days of trace content by default, and a full disk stops Postgres and both stores with it. Give the host at least 200 GB free behind /var/lib/docker. With managed Postgres, give the database at least 150 GB and the host at least 50 GB. Measure your own rate in the first week: Measure datastore growth.

Oracle Cloud shapes: an OCPU is not always a vCPU

On VM.Standard.E4.Flex (AMD) one OCPU is two vCPUs, so a single OCPU already matches the 2-vCPU column. On Ampere VM.Standard.A1.Flex one OCPU is one vCPU, so request 2 to land on the same tier.

Ampere is arm64 and fully supported. Every published image is a multi-arch manifest (linux/amd64 and linux/arm64), so docker compose up -d pulls the matching build with no change to .env. The Always Free allocation (4 OCPU / 24 GB on A1.Flex) covers the recommended tier.