Skip to content

Remote Server

Craft Agents can run as a remote server, letting you keep long-running sessions alive on a remote machine, access them from multiple clients (desktop app, browser, or CLI), and run compute-heavy tasks on a powerful server.

Choose the setup path that fits your deployment:

  • Run from source — install Bun (v1.0+)
  • Run the published container — install Docker

If you’re running from source, install Bun with:

Terminal window
curl -fsSL https://bun.sh/install | bash

Clone and run the install script:

Terminal window
git clone https://github.com/lukilabs/craft-agents-oss.git
cd craft-agents-oss
./scripts/install-server.sh

The script installs dependencies, generates a token, and prints the run command. Save your token — it cannot be recovered.

If you just want a deployable server, use the public GitHub Container Registry package:

Use the latest container tag:

Terminal window
export CRAFT_SERVER_TOKEN=$(openssl rand -hex 32)
echo $CRAFT_SERVER_TOKEN # Save this
docker run -d \
--name craft-agents-server \
--restart unless-stopped \
-p 9100:9100 \
-e CRAFT_SERVER_TOKEN=$CRAFT_SERVER_TOKEN \
-e CRAFT_RPC_HOST=0.0.0.0 \
-v craft-agents-data:/home/craftagents/.craft-agent \
ghcr.io/lukilabs/craft-agents-server:latest

This image already includes the browser-accessible Web UI, so you can open http://your-server:9100 immediately after the container starts. For any networked deployment, terminate TLS at a reverse proxy or mount certificates and set CRAFT_RPC_TLS_CERT / CRAFT_RPC_TLS_KEY.

If you prefer Docker Compose:

services:
craft-agents:
image: ghcr.io/lukilabs/craft-agents-server:latest
restart: unless-stopped
ports:
- "9100:9100"
environment:
CRAFT_SERVER_TOKEN: ${CRAFT_SERVER_TOKEN}
CRAFT_RPC_HOST: 0.0.0.0
volumes:
- craft-agents-data:/home/craftagents/.craft-agent
volumes:
craft-agents-data:

Sessions return empty responses (no errors)

When running with --user and a custom HOME directory, the Claude Agent SDK needs $HOME/.claude/ to be writable. If the HOME directory is read-only (e.g., owned by root), the SDK silently returns empty responses with no error logged.

Fix: Mount a writable volume at the HOME path. For example, if running as a host user whose home is /Users/alice:

Terminal window
docker run -d \
--name craft-agents-server \
--user $(id -u):$(id -g) \
-e HOME=/Users/alice \
-e CRAFT_SERVER_TOKEN=$CRAFT_SERVER_TOKEN \
-p 9100:9100 \
-v /Users/alice/.craft-agent/docker-home:/Users/alice \
-v /Users/alice/.craft-agent:/Users/alice/.craft-agent \
ghcr.io/lukilabs/craft-agents-server:latest

The volume stack works in layers:

  1. docker-home/Users/alice — writable HOME for the SDK (~/.claude/)
  2. .craft-agent/Users/alice/.craft-agent — workspace data (overlays on top)

Web UI not loading

If the server starts but http://your-server:9100 returns a 404 or connection error:

  • CRAFT_WEBUI_DIR not set — The Docker image sets this to /app/apps/webui/dist by default. If your docker-compose.yml or .env file overrides environment variables, make sure CRAFT_WEBUI_DIR is included or not overridden to empty.
  • Volume shadows the app directory — Mounting a volume over /app replaces the built WebUI assets. Only mount volumes to /home/craftagents/.craft-agent (or your custom HOME path), not to /app.
  • Older image tag — Pre-0.8.0 images don’t include the WebUI. Use latest or 0.8.0+.

Verify inside the container:

Terminal window
docker exec craft-agents-server ls /app/apps/webui/dist/index.html
docker exec craft-agents-server echo $CRAFT_WEBUI_DIR
Terminal window
git clone https://github.com/lukilabs/craft-agents-oss.git
cd craft-agents-oss
bun install

Generate a token and start:

Terminal window
export CRAFT_SERVER_TOKEN=$(openssl rand -hex 32)
echo $CRAFT_SERVER_TOKEN # Save this
CRAFT_SERVER_TOKEN=$CRAFT_SERVER_TOKEN \
CRAFT_RPC_HOST=0.0.0.0 \
CRAFT_RPC_TLS_CERT=certs/cert.pem \
CRAFT_RPC_TLS_KEY=certs/key.pem \
bun run packages/server/src/index.ts

For development TLS, generate a self-signed certificate:

Terminal window
./scripts/generate-dev-cert.sh
# Creates certs/cert.pem and certs/key.pem (valid 365 days)

For production, use certificates from a trusted CA (e.g., Let’s Encrypt) or place the server behind a reverse proxy (nginx, Caddy) that terminates TLS.

The server prints connection details on startup:

CRAFT_SERVER_URL=wss://0.0.0.0:9100
CRAFT_SERVER_TOKEN=<your-token>

The server can serve a browser-accessible web UI on the same port.

  • Docker image: already includes the Web UI
  • Run from source: build and enable it with:
Terminal window
# Build the web UI assets
bun run webui:build
# Start the server with Web UI enabled
CRAFT_SERVER_TOKEN=$CRAFT_SERVER_TOKEN \
CRAFT_WEBUI_DIR=apps/webui/dist \
CRAFT_RPC_HOST=0.0.0.0 \
bun run packages/server/src/index.ts

Or use the convenience script that builds everything:

Terminal window
bun run server:prod

Open https://your-server:9100 (or http:// without TLS) in any browser. You’ll see a login page.

Enter the server token as the password. The server issues a session cookie on successful login.

  • Login attempts are rate-limited to 5 per 60 seconds per IP
  • The session persists until you log out or the cookie expires

The web UI provides the same session interface as the desktop app — create sessions, send messages, manage workspaces. OAuth flows for Claude and Copilot work directly in the browser.

Connect to a remote server while keeping local workspaces:

  1. Click the workspace dropdown in the sidebar
  2. Select Add Workspace…Connect to Remote Server
  3. Enter the server URL (e.g., wss://192.168.1.100:9100) and token
  4. Click Test Connection to verify
  5. Select an existing workspace or create a new one on the server

Once connected, remote workspaces appear in your workspace switcher alongside local ones. A CloudOff icon indicates when a remote workspace is unreachable.

Launch the app as a pure thin client — all logic runs on the server:

Terminal window
CRAFT_SERVER_URL=wss://your-server:9100 \
CRAFT_SERVER_TOKEN=<token> \
bun run electron:start

Open the server URL in any browser and log in with the token. See Web UI above.

Use the terminal client for scripting and automation:

Terminal window
export CRAFT_SERVER_URL=wss://your-server:9100
export CRAFT_SERVER_TOKEN=<token>
craft-cli ping
craft-cli sessions
craft-cli send abc-123 "Run the tests"

See the CLI guide for the full command reference.

VariableRequiredDefaultDescription
CRAFT_SERVER_TOKENYesBearer token for authentication
CRAFT_SERVER_URLNoServer URL for client connections
CRAFT_RPC_HOSTNo127.0.0.1Bind address (0.0.0.0 for remote)
CRAFT_RPC_PORTNo9100Bind port
CRAFT_RPC_TLS_CERTYes*PEM certificate file (enables wss://)
CRAFT_RPC_TLS_KEYYes*PEM private key file
CRAFT_RPC_TLS_CANoPEM CA chain file (optional)
CRAFT_DEBUGNofalseEnable debug logging

* Required for remote connections. Can be omitted for localhost-only development.

Create the environment file at /path/to/craft-agents-oss/.env:

Terminal window
CRAFT_SERVER_TOKEN=<your-token>
CRAFT_RPC_HOST=0.0.0.0
CRAFT_RPC_PORT=9100
CRAFT_RPC_TLS_CERT=/path/to/cert.pem
CRAFT_RPC_TLS_KEY=/path/to/key.pem

Create a service file at /etc/systemd/system/craft-agents.service:

[Unit]
Description=Craft Agents Server
After=network.target
[Service]
Type=simple
User=<your-user>
WorkingDirectory=/path/to/craft-agents-oss
EnvironmentFile=/path/to/craft-agents-oss/.env
ExecStart=/home/<your-user>/.bun/bin/bun run packages/server/src/index.ts
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

Enable and start:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable craft-agents
sudo systemctl start craft-agents
# Check status
sudo systemctl status craft-agents
# View logs
journalctl -u craft-agents -f

Create a plist at ~/Library/LaunchAgents/com.craft.agents-server.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.craft.agents-server</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOU/.bun/bin/bun</string>
<string>run</string>
<string>packages/server/src/index.ts</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/craft-agents-oss</string>
<key>EnvironmentVariables</key>
<dict>
<key>CRAFT_SERVER_TOKEN</key>
<string>YOUR_TOKEN</string>
<key>CRAFT_RPC_HOST</key>
<string>0.0.0.0</string>
<key>CRAFT_RPC_PORT</key>
<string>9100</string>
<key>CRAFT_RPC_TLS_CERT</key>
<string>/path/to/cert.pem</string>
<key>CRAFT_RPC_TLS_KEY</key>
<string>/path/to/key.pem</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/craft-agents.log</string>
<key>StandardErrorPath</key>
<string>/tmp/craft-agents.log</string>
</dict>
</plist>

Load and start:

Terminal window
launchctl load ~/Library/LaunchAgents/com.craft.agents-server.plist
# Check status
launchctl list | grep craft
# View logs
tail -f /tmp/craft-agents.log
# Stop and unload
launchctl unload ~/Library/LaunchAgents/com.craft.agents-server.plist

Exposing the server directly to the internet is not recommended. Instead, use one of these approaches:

Tailscale creates a private mesh network between your devices. Install it on both the server and client machines — no port forwarding, no certificates, no firewall rules needed.

Terminal window
# On the server: bind to Tailscale IP only
CRAFT_RPC_HOST=100.x.y.z \
CRAFT_SERVER_TOKEN=$TOKEN \
bun run packages/server/src/index.ts

Traffic is encrypted end-to-end by Tailscale, so you can skip TLS certificate setup entirely. The server is only reachable from your Tailscale network.

Place the server behind a reverse proxy that handles TLS termination and access control. This is the standard approach for production deployments.

Caddy (automatic HTTPS):

craft.example.com {
reverse_proxy localhost:9100
}

nginx:

server {
listen 443 ssl;
server_name craft.example.com;
ssl_certificate /etc/letsencrypt/live/craft.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/craft.example.com/privkey.pem;
location / {
proxy_pass http://localhost:9100;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}

When using a reverse proxy, bind the server to localhost only (CRAFT_RPC_HOST=127.0.0.1) and let the proxy handle external access.

Cloudflare Tunnel exposes your server over HTTPS without opening ports or managing certificates. Install cloudflared and run:

Terminal window
# Quick tunnel — instant HTTPS URL, no config needed
cloudflared tunnel --url http://localhost:9100

This prints a https://<random>.trycloudflare.com URL you can open in any browser. For a permanent custom domain:

Terminal window
# One-time setup
cloudflared tunnel login
cloudflared tunnel create craft-agents
cloudflared tunnel route dns craft-agents agents.yourdomain.com
# Run the tunnel
cloudflared tunnel run --url http://localhost:9100 craft-agents

For quick, ad-hoc access without any setup:

Terminal window
# On the client: forward local port 9100 to the remote server
ssh -L 9100:localhost:9100 user@your-server

Then connect to ws://localhost:9100 from the desktop app or browser. The tunnel encrypts all traffic over SSH.

The built-in browser bridges from the remote server to the connected desktop client — pages open on your local machine, using your cookies and signed-in sessions. The browser tool is available when at least one desktop client is connected; web UI and CLI clients cannot host browser windows.

See Browser on remote workspaces for the security model and the allowRemoteEvaluate switch.

The server includes its version in the connection handshake. When a client connects to an older server (pre-0.8.0), it shows a warning that some features may not be available.