Attractor Attractor Docs 🔍

Docker

Overview

Attractor uses two Docker images published to the GitHub Container Registry:

  • ghcr.io/coreydaley/attractor-base — A base image built on Ubuntu 24.04 LTS (Noble) containing the Java 25 JRE and all system tools (graphviz, git, python3, ruby, nodejs, golang-go, rustc, docker, and more). It is only rebuilt when docker/Dockerfile.base changes between releases, keeping release builds fast.
  • ghcr.io/coreydaley/attractor — The server image. Built on every release by copying the pre-built JAR on top of attractor-base. On a typical release where only the code changes, this step completes in ~2 minutes instead of ~8.

The container runs as a non-root user (attractor). All persistent state — the SQLite database and pipeline output — is written to mounted volumes.

Quick Start

The easiest way to run Attractor is with Docker Compose using the docker/compose.yml file in the repository:

docker compose -f docker/compose.yml up -d
# or:
make docker-up

Open http://localhost:7070 once the container is running.

Image Tags

Each release publishes the following tags for the server image (ghcr.io/coreydaley/attractor):

TagExampleNotes
latestlatestMost recent release
<major>.<minor>.<patch>1.2.3Exact release — recommended for production
<major>.<minor>1.2Latest patch in this minor series
<major>1Latest release in this major series

The base image (ghcr.io/coreydaley/attractor-base) is tagged the same way, but only on releases where docker/Dockerfile.base has changed. The server image always uses attractor-base:latest.

Docker Compose

docker/compose.yml is the recommended way to run Attractor. It supports optional profiles for Ollama, PostgreSQL, and Docker-out-of-Docker, and mounts named volumes for all persistent data.

Default (SQLite)

docker compose -f docker/compose.yml up -d
# or:
make docker-up

With Ollama (local LLM)

docker compose -f docker/compose.yml --profile ollama up -d
# or:
make docker-up PROFILES=ollama

Configure Attractor to reach the Ollama container by adding these to your .env:

ATTRACTOR_CUSTOM_API_ENABLED=true
ATTRACTOR_CUSTOM_API_HOST=http://ollama
ATTRACTOR_CUSTOM_API_PORT=11434
ATTRACTOR_CUSTOM_API_MODEL=llama3.2

Pull a model once both containers are running:

docker compose -f docker/compose.yml exec ollama ollama pull llama3.2

With PostgreSQL

docker compose -f docker/compose.yml --profile postgres up -d
# or:
make docker-up PROFILES=postgres

Add these to your .env to point Attractor at the Compose-managed database:

ATTRACTOR_DB_TYPE=postgresql
ATTRACTOR_DB_HOST=postgres
ATTRACTOR_DB_PORT=5432
ATTRACTOR_DB_NAME=attractor
ATTRACTOR_DB_USER=attractor
ATTRACTOR_DB_PASSWORD=attractor

With Docker-out-of-Docker

Mounts the host Docker socket into the container so that the docker CLI inside Attractor talks to the host daemon. Containers launched from within Attractor are siblings on the host, not children of the Attractor container.

make docker-up PROFILES=docker
# or:
make docker-run PROFILES=docker   # local image

Security warning: Mounting /var/run/docker.sock grants the container root-equivalent access to the host. Any process that can reach the Docker daemon can create privileged containers with full host filesystem access. Only enable this if you understand and accept that risk.

If your host’s docker group GID differs from 999, set DOCKER_GID in your .env:

# Find your docker group GID:
getent group docker | cut -d: -f3

# Then add to .env:
DOCKER_GID=<gid>

Combining profiles

docker compose -f docker/compose.yml --profile ollama --profile postgres up -d
# or:
make docker-up PROFILES="ollama postgres"
make docker-up PROFILES="docker ollama"
make docker-up PROFILES="docker ollama postgres"

Stopping

docker compose -f docker/compose.yml down
# or:
make docker-down

Named volumes (attractor-data, attractor-projects, ollama-data, postgres-data) are preserved when you stop. Pass --volumes to remove them too.

CLI Subprocess Mode

CLI subprocess mode is not supported when running Attractor inside Docker. The AI CLI tools (claude, codex, gemini, copilot) are not installed in the container, and their authentication state is not available inside the container.

Use Direct API mode instead — pass your API keys as environment variables at runtime. See Passing API Keys below.

Note: The docker CLI is available in the container and can be connected to the host daemon via the Docker-out-of-Docker profile.


Passing API Keys

API keys are passed as environment variables at run time — they are never baked into the image.

cp .env.example .env
# edit .env and fill in your keys
make docker-up        # Compose picks up .env automatically

Docker Compose loads .env from the project root automatically. When running docker run directly, pass keys via --env-file .env or individual -e KEY=value flags.

Environment Variables

LLM Provider API Keys

VariableDescription
ATTRACTOR_ANTHROPIC_API_KEYAPI key for Anthropic Claude (Direct API mode)
ATTRACTOR_OPENAI_API_KEYAPI key for OpenAI GPT (Direct API mode)
ATTRACTOR_GEMINI_API_KEYAPI key for Google Gemini (Direct API mode)
ATTRACTOR_GOOGLE_API_KEYAlternative to ATTRACTOR_GEMINI_API_KEY

Custom OpenAI-Compatible API

These env vars bootstrap the custom provider settings on first start. Values saved through the Settings UI take precedence on subsequent starts.

VariableDefaultDescription
ATTRACTOR_CUSTOM_API_ENABLEDfalseSet to true to enable the custom provider
ATTRACTOR_CUSTOM_API_HOSThttp://localhostBase URL of the OpenAI-compatible endpoint
ATTRACTOR_CUSTOM_API_PORT11434Port number (leave blank to omit from URL)
ATTRACTOR_CUSTOM_API_KEYAPI key (optional — Ollama does not require one)
ATTRACTOR_CUSTOM_API_MODELllama3.2Model name to use for requests

Storage

VariableDefaultDescription
ATTRACTOR_DB_TYPEsqliteDatabase backend: sqlite, mysql, or postgresql
ATTRACTOR_DB_NAME/app/data/attractor.dbDatabase name or SQLite file path
ATTRACTOR_DB_HOSTDatabase server hostname (MySQL/PostgreSQL)
ATTRACTOR_DB_PORTDatabase port (defaults by type)
ATTRACTOR_DB_USERDatabase username
ATTRACTOR_DB_PASSWORDDatabase password
ATTRACTOR_DB_URLFull JDBC URL — overrides all individual ATTRACTOR_DB_* vars
ATTRACTOR_PROJECTS_DIR/app/projectsDirectory where pipeline output is written (logs, workspaces, artifacts)

Docker-out-of-Docker

VariableDefaultDescription
DOCKER_GID999GID of the docker group on the host. Only needed when using PROFILES=docker and your host GID differs from 999. Find it with getent group docker | cut -d: -f3.

Debug

VariableDefaultDescription
ATTRACTOR_DEBUGSet to any non-empty value to enable debug logging and stack traces

Volumes

Container pathCompose volumeDescription
/app/dataattractor-dataSQLite database and persistent app data
/app/projectsattractor-projectsPipeline output: stage logs, workspaces, checkpoints, and artifacts

Both paths must be mounted to preserve state across container restarts and upgrades.

Ports

PortDescription
7070Web UI and REST API. Map with -p <host-port>:7070. Override the host port with ATTRACTOR_PORT in your .env.

Building Locally

make docker-build-base   # build the base image (attractor-base:local)
make docker-build        # build the server image (attractor:local); builds base if not present
make docker-run          # run attractor:local, auto-loads .env if present

make docker-build checks for attractor-base:local and only rebuilds it when missing, so it is safe to run repeatedly.

Or directly with Docker:

# Build base first (only needed once, or when docker/Dockerfile.base changes)
docker build -f docker/Dockerfile.base -t attractor-base:local .

# Build server image
docker build -f docker/Dockerfile -t attractor:local .

# Run with both volumes mounted
docker run --rm -p 7070:7070 \
  -v "$(pwd)/data:/app/data" \
  -v "$(pwd)/projects:/app/projects" \
  attractor:local