Zero to a running Phantom in 10 minutes.
This guide walks you through every step. If you get stuck, open an issue on GitHub.
- An Anthropic API key. Get one at console.anthropic.com. Starts with
sk-ant-. - Docker and Docker Compose. Install from docs.docker.com/engine/install. If
docker compose versionprints a version number, you are good. - A Slack workspace (optional, but recommended). This is how you talk to your Phantom. Any workspace where you can install apps works.
That is it. No Bun, no Node, no git clone. Docker handles everything.
Skip this section if you want to run Phantom without Slack. You can always add it later.
This takes about 5 minutes. The repo includes a manifest file that configures everything automatically.
- Go to api.slack.com/apps
- Click Create New App
- Choose From an app manifest
- Select the workspace you want to install into
- Switch to the YAML tab
- Copy the entire contents of
slack-app-manifest.yamland paste it in - Click Next, review the summary, then click Create
- On the next page, click Install to Workspace and approve the permissions
The manifest creates the app with the name "Phantom". If you want a different name, go to Settings > Basic Information and change App Name to whatever you want. Do this after creating the app, not by editing the manifest.
You need three values from the Slack app you just created.
Bot Token:
- In the sidebar, go to OAuth & Permissions
- Copy the Bot User OAuth Token. It starts with
xoxb-.
App Token:
- In the sidebar, go to Basic Information
- Scroll to App-Level Tokens and click Generate Token and Scopes
- Name it anything (e.g., "socket")
- Click Add Scope and select
connections:write - Click Generate
- Copy the token. It starts with
xapp-.
Your Slack User ID:
- In the Slack desktop app, click your name or profile picture
- Click the three dots menu
- Click Copy member ID
- It starts with
U(e.g.,U04ABC123XY)
You now have three values: a xoxb- token, a xapp- token, and a U... user ID. Keep them handy.
Download the compose file and env template:
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/docker-compose.user.yaml -o docker-compose.yaml
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/.env.example -o .envOpen .env in your editor and fill in these values:
ANTHROPIC_API_KEY=sk-ant-your-key-here
Your Anthropic API key. This is the only value you absolutely must set.
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
OWNER_SLACK_USER_ID=U04ABC123XY
SLACK_BOT_TOKEN- The bot token from OAuth & Permissions (starts withxoxb-).SLACK_APP_TOKEN- The app-level token you generated (starts withxapp-).OWNER_SLACK_USER_ID- Your Slack user ID (starts withU). Only this user can talk to Phantom. If you leave this blank, anyone in your workspace can message it.
PHANTOM_NAME=phantom
PHANTOM_MODEL=claude-sonnet-4-6
PHANTOM_NAME- What your Phantom calls itself. Default:phantom.PHANTOM_MODEL- The Claude model. Options:claude-sonnet-4-6(default, recommended) orclaude-opus-4-6(more capable, higher cost).
Everything else in .env.example has sensible defaults. You can leave the rest commented out.
If you just want the shortest path to a running Phantom with Slack:
ANTHROPIC_API_KEY=sk-ant-your-key-here
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
OWNER_SLACK_USER_ID=U04ABC123XY
Four lines. That is all Phantom needs.
docker compose up -dThat is the entire command.
- Docker pulls three images: Phantom, Qdrant (vector database for memory), and Ollama (embedding model).
- The Phantom container waits for Qdrant and Ollama to be ready.
- Ollama downloads the
nomic-embed-textembedding model (~270MB). This only happens once. - Phantom runs
phantom init --yes, which generates config files from your .env values. - Phantom connects to Slack and sends you a DM saying it is ready.
First boot takes 2-3 minutes because of the model download. After that, restarts take about 15-20 seconds.
If you want to see what is happening during first boot:
docker logs phantom -fPress Ctrl+C to stop following. The important lines to look for:
[phantom] Qdrant is ready
[phantom] Ollama is ready
[phantom] Model pull complete
[phantom] Configuration initialized
[phantom] Starting Phantom...
curl http://localhost:3100/healthYou should get a JSON response with "status":"ok". It includes the agent name, Slack connection status, and memory system status.
If you configured Slack, your Phantom should have sent you a direct message. Open Slack and look for a DM from it. Say hello. It will respond.
If you do not see a DM, check the logs:
docker logs phantom --tail 50docker psYou should see three containers running: phantom, phantom-qdrant, and phantom-ollama.
Running Phantom on your laptop works great for trying it out. For a persistent setup that runs 24/7, put it on a cloud VM.
Any cloud provider works: Hetzner, DigitalOcean, AWS, GCP, Linode, Vultr. The minimum spec:
- 2 vCPU
- 4 GB RAM
- 40 GB disk
- Ubuntu 22.04 or newer
SSH into your VM and install Docker:
# Install Docker (official method for Ubuntu/Debian)
curl -fsSL https://get.docker.com | shCreate a directory and download the files:
mkdir -p ~/phantom && cd ~/phantom
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/docker-compose.user.yaml -o docker-compose.yamlCreate your .env file:
cat > .env << 'EOF'
ANTHROPIC_API_KEY=sk-ant-your-key-here
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
OWNER_SLACK_USER_ID=U04ABC123XY
PHANTOM_NAME=your-phantom-name
PHANTOM_MODEL=claude-sonnet-4-6
EOFStart it:
docker compose up -dWait 2-3 minutes for first boot, then verify:
curl http://localhost:3100/healthThe Phantom container needs access to the Docker socket so the agent can create sibling containers for code execution. The compose file sets this up with group_add using a default GID of 988, which works on most cloud VMs.
If you get Docker permission errors, find your Docker socket's group ID and set it in .env:
# Find your Docker socket GID
stat -c '%g' /var/run/docker.sock
# Add to .env
echo "DOCKER_GID=YOUR_GID_HERE" >> .env
# Restart
docker compose down && docker compose up -dIf you want Phantom accessible on a public domain (e.g., phantom.yourdomain.com):
- Point a DNS A record to your VM's IP address
- Install Caddy as a reverse proxy:
sudo apt install caddy
# Create Caddyfile
sudo tee /etc/caddy/Caddyfile << 'EOF'
phantom.yourdomain.com {
reverse_proxy localhost:3100
}
EOF
sudo systemctl restart caddyCaddy handles HTTPS certificates automatically. Your Phantom is now at https://phantom.yourdomain.com.
# Stop (keeps all data)
docker compose down
# Start again
docker compose up -d
# Stop and destroy all data (memory, config, evolved state)
docker compose down -vAll persistent state lives in Docker volumes. docker compose down preserves them. Only docker compose down -v deletes them.
When a new version of Phantom is published:
docker compose pull phantom
docker compose up -d phantomThis pulls the latest image and restarts only the Phantom container. Qdrant, Ollama, memory, config, and evolved state are preserved.
Something else is listening on port 3100. Either stop it, or change the port in your .env:
echo "PORT=3200" >> .env
docker compose down && docker compose up -dThen check health at http://localhost:3200/health.
- Verify your
SLACK_BOT_TOKENstarts withxoxb-and yourSLACK_APP_TOKENstarts withxapp-. - Make sure Socket Mode is enabled on your Slack app (the manifest does this automatically).
- Check that the app is installed to your workspace (not just created).
Check if all containers are running:
docker psIf phantom-qdrant or phantom-ollama is missing or restarting, check their logs:
docker logs phantom-qdrant --tail 20
docker logs phantom-ollama --tail 20Common cause: not enough memory. Qdrant and Ollama together need about 2 GB of RAM.
Ollama needs internet access for the first download of nomic-embed-text (~270MB). If it fails:
# Check Ollama logs
docker logs phantom-ollama --tail 20
# Try pulling manually
docker exec phantom-ollama ollama pull nomic-embed-textIf OWNER_SLACK_USER_ID is set, only that user can talk to Phantom. Everyone else gets a polite rejection. Double-check the user ID matches yours.
If OWNER_SLACK_USER_ID is not set, anyone in the workspace can message it.
The compose file sets explicit DNS servers (1.1.1.1 and 8.8.8.8) to avoid issues with Ubuntu's systemd-resolved. If you are behind a corporate firewall that blocks external DNS, you may need to change these in the compose file.
Once Phantom is running, you can connect Claude Code to it as an MCP server. This lets Claude Code use your Phantom's memory, tools, and capabilities.
Generate a token:
docker exec phantom bun run phantom token create --client claude-code --scope operatorAdd the connection to your Claude Code MCP config (usually ~/.claude/settings.json or project-level .mcp.json):
{
"mcpServers": {
"phantom": {
"type": "streamableHttp",
"url": "http://localhost:3100/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}If Phantom is on a remote VM with a domain, replace http://localhost:3100 with https://your-phantom.yourdomain.com.
Or just ask your Phantom in Slack: "Create an MCP token for Claude Code." It will generate the token and give you the config snippet.
- Channels - add Telegram, email, and webhook integrations
- MCP - connect external clients and other Phantoms
- Roles - customize your Phantom's specialization
- Self-Evolution - how the agent improves over time
- Security - auth, secrets, permissions, and hardening
- Architecture - understand the system design