Skip to content

Set up a server from scratch

This page covers three starting points — jump to yours:

  • From zero — no server at all. Rent a small Linux server, run one install command, come back with a box address and box code.
  • Agents — your box is connected but the scan found no coding agent. Install one.
  • Models — connect a model: a subscription login or an API key, on your box, on your terms.

You’ll rent a small Linux server, run one install command, add a model key, and come back to the Gray app holding the box address and one-time box code the installer prints. Budget about fifteen minutes. Already have a machine — a VPS, a homelab, a spare laptop? Skip the first step and go straight to Set up your box.

Gray’s needs are modest: Linux (Ubuntu 24.04 or Debian 12), 2 GB RAM, Python 3.11+, systemd — the smallest tier at most providers, around $5/month. Full list: Compatibility.

Two providers we test against (any equivalent works):

  • Hetzner Cloud — create a server: image Ubuntu 24.04, type CPX11 (2 GB), add your SSH key, create. Our release tests run on exactly this tier.
  • DigitalOcean — create a Basic Droplet: Ubuntu 24.04, the 2 GB option, add your SSH key, create.

Either way you end with an IP address. Connect to it:

Terminal window
ssh root@YOUR_SERVER_IP

On the server:

Terminal window
curl -fsSL https://get.layergray.com | sudo sh

The installer handles everything: dependencies, a Python virtualenv, a 0600-tight env file at /etc/gray/server.env, Tailscale when it’s missing, and the gray-server systemd service. It then waits for the server to come up and ends on a PAIR YOUR PHONE card with the two things the app asks for:

  • Box address — an https://…ts.net:8443 URL on your tailnet, and
  • your one-time box code — the proof that you own this box. (The server’s own log calls it a claim code — same thing.)

Keep both on screen; you’ll paste them into the app in step 6.

If the run is interrupted, just run the same command again — the installer is safe to re-run and repairs a half-finished install.

3. Tailscale — how your phone reaches the box

Section titled “3. Tailscale — how your phone reaches the box”

Your box and your phone must be on the same tailnet — the app talks to the box address from step 2, which only exists inside it. The installer installs Tailscale and publishes the server for you (HTTPS on port 8443); the only part it can’t do is sign the box in. If the installer says Tailscale isn’t up yet:

Terminal window
sudo tailscale up

…then re-run the install one-liner (it’s safe) so it finishes the publish step, or run the exact command the installer printed:

Terminal window
sudo tailscale serve --yes --bg --https=8443 http://127.0.0.1:8848

Enable MagicDNS and HTTPS certificates in the Tailscale admin console (Settings → DNS), and install the Tailscale app on your phone, signed in to the same tailnet. Why it works this way — no public ports, real certificates: Tailscale.

Add a model credential next — see Models below. The short version: an Anthropic key (or Claude subscription login) powers the brain; an OpenAI key optionally adds premium realtime voice. Keys go in /etc/gray/server.env and stay on your hardware.

  • No box code in the installer output — read it from the box’s journal (the log line says “claim code” — that’s the box code): journalctl -u gray-server | grep -i 'claim code'. More: Box code.
  • The box is already claimed (“This box is already claimed…”) — sign in as the account that set it up, or have that owner release it (Me → Account → delete account on the box) and sign in fresh — the box prints a new box code. Full story: Box code.
  • A sign-in code never arrives — a box with no email sender prints the 6-digit code to its journal instead. The app shows this exact command, tap-to-copy: journalctl -u gray-server | grep -i 'sign-in code'. See Sign in.
  • “Checking box address…” never succeeds — the phone can’t reach the box. Check the Tailscale app on your phone is connected to the same tailnet, and MagicDNS + HTTPS certificates are enabled. More: Connection problems.
  • The install died midway — run the one-liner again; it self-repairs.
  • The server won’t come upsystemctl status gray-server and journalctl -u gray-server -e show why. If the installer said port 8848 is busy, something else owns it — re-run with --port.

Open Gray. You create your account first — email, then the 6-digit code — and land on Connect your box. The screen shows the same install command with a copy button; you’ve already run it, so paste the box address and box code from step 2 and tap Connect box. Gray checks the address, claims the box, walks you through a first win and a notifications ask, then scans the box for agents and models. When you see Scan complete, you’re done — say your first command: Your first voice command.

(Adding a box to an already-running Gray later is the same idea from Servers → Add a server, which can also auto-detect on the tailnet or take a pairing code — see Servers.)

Your box is connected but the scan came back empty — no coding agent installed. Gray works through agents that live on the box; it detects these seven on the box’s PATH:

Claude Code · Codex · Grok · Gemini · Aider · OpenCode · Cursor

Install at least one. Claude Code is the one Gray’s brain runs on, so start there — on the box:

Terminal window
npm install -g @anthropic-ai/claude-code

(Node 18+ — apt install nodejs npm on a stock box gets you there. Each of the other agents installs per its own guide; Gray finds whatever appears on the PATH.)

Then rescan from the app: Me → Brain → Stack → Rescan. Installed agents show up under Agents, and their local sessions appear in Activity. Next: connect a model — Models.

An agent needs a model behind it. There are exactly two ways to connect one, and both keep the same boundary: credential acquisition happens on the provider’s own site or console — Gray never captures or automates it, and keys stay on the box. There is no API-key field anywhere in the Gray app.

Path 1 — subscription login. Run the agent’s own login on the box; it hands you to the provider’s own browser sign-in, and the session token lands on the box through the agent’s own flow. Gray never sees it. For the brain:

Terminal window
claude # follow the sign-in prompt — uses your Claude subscription

Path 2 — API key (BYOK). Create the key in the provider’s own console, paste it into the box’s env file, restart. It stays on the box:

Terminal window
nano /etc/gray/server.env
# ANTHROPIC_API_KEY=sk-ant-...
# OPENAI_API_KEY=sk-... (optional — premium realtime voice)
systemctl restart gray-server

What each credential powers, and where to create it:

ProviderWhat Gray uses it forGet itBilling
Claude (Anthropic)The brain — conversation, agents, summaries. Subscription login or ANTHROPIC_API_KEYclaude.ai (subscription) · console.anthropic.com (API key)Flat monthly (subscription) · usage-based, prepaid credits (API)
OpenAIPremium realtime voice + transcription — optional; on-device voice works without itplatform.openai.comUsage-based, prepaid credits
Grok / xAIThe Grok agent CLI, when you use itconsole.x.aiUsage-based
Gemini (Google)The Gemini agent CLI, when you use itaistudio.google.comUsage-based, free tier available

The brain itself runs on Claude — the other logins power their own agent CLIs on the box. After adding a credential, rescan (Me → Brain → Stack → Rescan): Gray reports presence only — a detected login or a masked last-4 fingerprint of an env key — never the secret itself. Where keys live and why: Key storage · full detection list: Models & keys · enterprise routing (Bedrock, Vertex, proxies): Configuration reference.