Skip to main content

Self-host AcruxCore with Docker

AcruxCore publishes three prebuilt images on Docker Hub. This guide runs them with Docker Compose, first on your own machine and then on a server. You do not need to clone the repository or build anything.

You need Docker with the Compose plugin (docker compose, not the older docker-compose). On the first start, the three AcruxCore images download about 300 MB.

What runs​

A self-hosted AcruxCore is five containers:

ContainerImageWhat it does
apiacruxcore/apiThe API and the gateway. It applies database migrations each time it starts.
workeracruxcore/workerBackground jobs: evaluation runs, email and the weekly digest.
webacruxcore/webThe dashboard, served by nginx. It forwards /api/ to the api container.
postgrespostgres:16-alpineEvery prompt, tool, trace and account.
redisredis:7-alpineThe job queue between the API and the worker.

The hosted AcruxCore at acruxcore.com runs these same images.

Try it on your machine​

1. Start the stack​

Download the local Compose file and start it:

mkdir acruxcore && cd acruxcore
curl -O https://raw.githubusercontent.com/AcruxCore/AcruxCore/main/docker-compose.local.yml
docker compose -f docker-compose.local.yml up -d --wait

--wait returns when every container reports healthy. On our machine that took 13 seconds after the download.

This file is for a trial on your own machine. Every secret in it has a fixed default value, and email is turned off. Do not expose it to a network.

The dashboard is at http://localhost:8080, and the API is at http://localhost:3001/api/v1. If one of those ports is taken, create a .env file next to the Compose file and choose others:

.env
WEB_PORT=8097
API_PORT=3097

Then run the same up command again.

2. Create the owner account​

A new instance has no users. The API prints a one-time link that creates the first account, and that account owns the instance. Read it from the API logs:

docker compose -f docker-compose.local.yml logs api | grep first-run
http://localhost:8080/first-run?token=MTc5NDI4…

Open the link, then enter your name, email and a password.

The first-run page, which creates the owner account

The link works once and expires after 60 minutes. Restart the api container to print a new one. Other people join later through team invites.

3. Make your first gateway call​

From here on, a self-hosted instance works like the hosted one. Follow the Quickstart from step 2, and set the base URL to your own API:

export ACRUXCORE_BASE_URL="http://localhost:3001/api/v1"
export ACRUXCORE_API_KEY="<the key from Account & keys>"

The SDKs read the same two variables. For example, with an OpenAI credential and a model registered under the public name gpt-6-luna, this call goes through your own gateway:

curl "$ACRUXCORE_BASE_URL/gateway/chat/completions" \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-6-luna","reasoning_effort":"none","messages":[{"role":"user","content":"Say hello in one short sentence."}]}'
{"id":"chatcmpl-EiWUkG48S2f2tqAkuRmmuXMWHHlyB","model":"gpt-6-luna","object":"chat.completion","created":1794280050,"choices":[{"index":0,"message":{"role":"assistant","content":"Hello, how can I help?"},"finish_reason":"stop"}],"usage":{"prompt_tokens":13,"completion_tokens":10,"total_tokens":23}}

The call is also recorded as a trace, which you can open under Traces in the dashboard.

Stop it​

docker compose -f docker-compose.local.yml down # keeps your data
docker compose -f docker-compose.local.yml down -v # also deletes the database

Run it on a server​

A server uses the other Compose file, docker-compose.yml. It differs from the local file in four ways:

  • You bring your own Postgres. A managed service such as RDS or Neon works, and so does a Postgres container you run yourself.
  • Every secret is required, and the API refuses to start without them.
  • NODE_ENV is production, so sessions and email behave as they do on acruxcore.com.
  • No container publishes a public port. You put a reverse proxy in front of web.

1. Download the files​

mkdir acruxcore && cd acruxcore
curl -O https://raw.githubusercontent.com/AcruxCore/AcruxCore/main/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/AcruxCore/AcruxCore/main/.env.docker.example

2. Fill in .env​

Generate each secret with a separate run of openssl rand -base64 32. Then set at least these values:

VariableValue
DATABASE_URLThe Postgres connection string the API uses at runtime.
DIRECT_URLA direct Postgres connection for migrations. If DATABASE_URL goes through a pooler such as PgBouncer, this one must not. Otherwise use the same value.
GATEWAY_ENCRYPTION_KEYEncrypts the provider keys you store. If you lose it, every stored provider key becomes unreadable, so back it up.
BETTER_AUTH_SECRETSigns session cookies. Changing it signs everyone out.
EMAIL_UNSUBSCRIBE_SECRETSigns the unsubscribe link in every email.
APP_URLThe URL people open in the browser, such as https://acruxcore.example.com. Links in emails use it, and sign-in accepts only this origin.
EMAIL_TRANSPORTsmtp for any mail server, ses for Amazon SES, or none to send no email.

With EMAIL_TRANSPORT=none, new accounts are verified at once, and invites and password resets send no email. The comments in .env list the settings each transport needs.

3. Pin a release​

Set the image tag to a release, so a later pull does not change the version you run:

.env
ACRUXCORE_IMAGE_TAG=2026.11.3

Each release has a tag such as 2026.11.3, and a GitHub release with upgrade notes. Without this line, Compose uses latest.

4. Create the network and start​

The stack joins an external Docker network named mynetwork, so a reverse proxy on the same host can reach web. Create the network once, then start the stack:

docker network create mynetwork
docker compose pull
docker compose up -d --no-build --wait

--no-build makes Compose use only the downloaded images. It never compiles anything on the server.

Point your reverse proxy at acruxcore-web:80 on mynetwork. The web container forwards /api/ to the API, so one host name serves both the dashboard and the API. If you want to reach the stack without a proxy, publish the port in a compose.override.yml next to the Compose file:

compose.override.yml
services:
web:
ports: ['8080:80']

The stack also publishes Redis on 127.0.0.1:6379. If another Redis already uses that port, set REDIS_PORT in .env to a free one.

Then create the owner account from the first-run link in docker compose logs api, as in step 2 above.

Upgrade to a new release​

  1. Read the release notes for every release between yours and the new one. They list new required variables.
  2. Back up the database. The API applies new migrations when it starts, and migrations are not reversed on a downgrade.
  3. Change ACRUXCORE_IMAGE_TAG in .env, then run:
docker compose pull
docker compose up -d --no-build --wait

On the local file, add -f docker-compose.local.yml to both commands.

Run your own changes​

To run a modified copy of AcruxCore, clone the repository and add --build to the up command. Compose then builds the three images from your checkout instead of downloading them. CONTRIBUTING.md covers running the apps from source without Docker.