# By hand

At the end, the runner is set up from files you wrote yourself, with the same result as the portal's line and the runner's page. Use it to see every setting, or when your company wants each line reviewed.

## At a glance

**Time:** 20 minutes.

**You need:**

- A Linux box with Docker: [Any Linux box](any-box.md) says what kind
- Your Loopgate sign-in as an owner or admin of your org
- The kit's files: [compose.yaml](../compose.yaml), [runner.env.example](../runner.env.example), [slack.env.example](../slack.env.example) and [tunnel.env.example](../tunnel.env.example); [install.sh](../install.sh) is what the portal's line runs, [laptop.compose.yaml](../laptop.compose.yaml) is for a laptop, and [README.md](../README.md) is the operator's reference

## Steps

Run every command in the `loopgate` folder. A line in a file always wins over the runner's page.

1. **Make the folder and download the kit's files.**

   ```sh
   mkdir -p ~/loopgate && cd ~/loopgate
   curl -fsSLO "https://loopgate.dev/runner/{compose.yaml,runner.env.example,slack.env.example}"
   cp runner.env.example runner.env && chmod 600 runner.env
   ```

2. **Fill in `runner.env`** (`nano runner.env`; save and leave with Ctrl+O, Enter, Ctrl+X):
   - `LOOPGATE_CONTROL_PLANE`: keep `https://app.loopgate.dev`.
   - `LOOPGATE_INSTALL_TOKEN`: delete the line to sign in with a device code in step 4, or paste a token from the portal's **Devices → Add a runner** and skip step 4.
   - `LOOPGATE_RUNNER_NAME`: the name on the portal's Devices page. Set it; otherwise the page shows the container's id.
   - `LOOPGATE_RUNNER_URL`: a permanent address (step 7). Leave it out for the temporary one.
   - `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`: an API key instead of signing in to a subscription.
   - `GH_TOKEN`, the three `GIT_CONFIG_` lines and the `GIT_AUTHOR_`/`GIT_COMMITTER_` names and emails: a GitHub token and the identity commits carry, instead of Settings → GitHub.

3. **Start it.**

   ```sh
   docker compose up -d
   ```

   You should see: the image download (about 170 MB the first time), then each container `Started`.

4. **Sign the runner in to your org.**

   ```sh
   docker compose exec runner loopgate login --no-open
   ```

   You should see: `Open <address>`, `Code: …` and `Waiting for confirmation in your browser.` Open the address on your laptop, sign in as an owner or admin and enter the code; the box then says `Signed in as <your email>.`

5. **Install and sign in to engines from the terminal.** Codex and Claude Code print an address and a code to open on your laptop; pi opens its own screen, where you choose **Trust (this session only)**, type `/login openai-codex` (choose **Device code login**) or `/login anthropic`, then `/quit`.

   ```sh
   docker compose exec runner loopgate engines install codex
   docker compose exec -it runner loopgate engines login codex
   docker compose exec runner loopgate engines install claude-code
   docker compose exec -it runner loopgate engines login claude-code
   docker compose exec -it runner pi
   ```

   > In pi, `/model` sets the model a step on pi runs when nobody picked one. The copilot picks GPT-6 Luna by itself.

6. **After a change to `runner.env`, restart the runner.**

   ```sh
   docker compose up -d runner
   ```

7. **For a permanent address**, make one with [Cloudflare Tunnel](address-cloudflare.md) (its token goes in `tunnel.env`) or [Tailscale](address-tailscale.md). Put the address on the `LOOPGATE_RUNNER_URL` line, with no slash at the end, and restart (step 6).

8. **For Slack from a file**, create the app and its two tokens as in [Connect Slack](slack-connect.md#steps), steps 2 to 4, then `cp slack.env.example slack.env && chmod 600 slack.env` and fill it in:
   - `SLACK_APP_TOKEN` and `SLACK_BOT_TOKEN`: the `xapp-` and `xoxb-` tokens.
   - `LOOPGATE_SLACK_WORKSPACE_ID`: the `T…` id in your Slack workspace's web address.
   - `LOOPGATE_SLACK_MEMBERS`: `<Slack id>:<member id>` pairs separated by commas. `docker compose exec runner loopgate team` lists the member ids; a Slack id is under **Copy member ID** on a person's profile.
   - `LOOPGATE_SLACK_REPO`: the workspace every task works in, by name or as `/repos/<name>`.
   - `LOOPGATE_SLACK_MAX_COST_USD` and `LOOPGATE_SLACK_MAX_HOURS`: the most one task may spend.
   - `LOOPGATE_RUNNER_URL`: the runner's address, for the thread's links.

   Then `docker compose up -d slack`. A `slack.env` with `SLACK_APP_TOKEN` wins over Settings → Slack.

9. **Check it.**

   ```sh
   docker compose exec runner loopgate whoami
   ```

   ```sh
   docker compose logs runner slack
   ```

   You should see: `Signed in as <your email>` with your org, `Runner ready at https://…`, and with a `slack.env`, `Loopgate Slack worker is connected for T…`.

## Good to know

- Open the runner from the portal's **Devices** page.
- Anything you left out of the files, the runner's page still sets: [Engines, GitHub and a repository](sign-in.md).

## If something is off

- **`<NAME> is required.` or `<NAME> must be a positive number.`** A line in `slack.env` is empty or wrong. Fix it, then `docker compose up -d slack`.
- **`LOOPGATE_SLACK_MEMBERS must read U0123:mem_abc,U0456:mem_def.`** The people line has a typo.
- **`Ignoring Slack events from workspace T….`** The app is installed in another Slack workspace than `LOOPGATE_SLACK_WORKSPACE_ID`.
- **Anything else.** [Updates and backups](care.md) says what each line means.

## Next

- [Engines, GitHub and a repository](sign-in.md)
- [Updates and backups](care.md)
