One click, and a self-hosted Coshell drive is running on your own cloud account.
Coshell is a multiplayer cloud terminal for engineering teams working with AI coding agents. Several people can prompt the same live agent session inside a shared environment with a filesystem, a command execution environment and an integrated localhost browser.
A drive is the persistent workspace that environment lives in. Self-hosted drives run on hardware you own, and the Coshell control plane only ever carries identity, access, attribution and audit. Your code never leaves your account.
This repo is how you get one running on a cloud provider without touching a terminal.
| Provider | |
|---|---|
| Render | |
| Railway | see Railway below |
| Northflank | see deploy/northflank.md |
| DigitalOcean / Hetzner / Vultr / Linode | see deploy/cloud-init.yaml |
You need one enrollment token. It is single-use and expires 15 minutes after minting, so get it immediately before deploying, not the day before.
- In the Coshell dashboard: New drive → Self-hosted
- Or on the command line:
coshell host token
The deploy flow asks for it as COSHELL_ENROLL_TOKEN. Set it, deploy, and the drive shows up in your
Coshell sidebar as soon as the tunnel connects.
If the token expires while the first build is queued — Render's first build can take several minutes — the container will say so in its logs. Mint a fresh one, update the environment variable, redeploy.
| Variable | Required | Default | |
|---|---|---|---|
COSHELL_ENROLL_TOKEN |
on first boot only | — | Single-use, 15-minute lifetime. Consumed once and then cleared. |
COSHELL_DRIVE_NAME |
no | Cloud drive |
What the drive is called in Coshell. |
COSHELL_EDGE_URL |
no | https://coshell.ai |
Only change this if you are pointed at a different Coshell environment. |
One container, from ghcr.io/shelltabhq/coshell-drive (public, no registry login), wrapped with a
first-boot step that exchanges your enrollment token for a registered drive.
Two things about the shape of it are worth knowing, because they are easy to get wrong if you build your own deployment:
A volume mounted at /data, always. Only /data persists. The drive keeps its database, the
workspace, HOME, git config and every package anyone installs under that path. Deploy without the
volume and every restart is total data loss.
No public port, ever. The drive listens on 127.0.0.1:4096 and reaches the Coshell edge over an
outbound WebSocket tunnel. It needs no inbound port, no public hostname and no inbound firewall rule.
Publishing 4096 only adds attack surface. It would also fail a platform health check, since every drive
route answers 401 to anything without edge-issued auth — which is why the Render Blueprint declares a
background worker rather than a web service.
Redeploy on your provider. The volume and the stored enrollment carry the drive forward, so it comes
back as the same drive — same sessions, same files — on the newer runtime. Clear the build cache if
your provider has cached the FROM layer.
The dashboard's Update now button does not work for a drive deployed this way, and that is expected. It drives a host supervisor that re-pulls the image, and a PaaS deployment has no equivalent. Redeploying is the update mechanism here.
To pin a specific runtime instead of tracking latest, set the COSHELL_DRIVE_TAG build argument.
You pay your provider directly for the compute. Coshell does not meter or bill self-hosted drives.
The honest version: a drive on a PaaS runs continuously, and most PaaS billing does not stop when it is idle. Coshell's own idle-sleep logic reduces Coshell's hosting bill, and it cannot reduce your provider's, because the provider bills for the service being deployed rather than for the drive doing work. Concretely:
- Railway bills measured usage by the second, around
$0.028per vCPU-hour and$0.014per GB-hour plus$0.15/GB/month for the volume, so an idle drive is genuinely cheap. Railway's Serverless auto-sleep, though, decides idleness from network traffic, and the drive holds a persistent tunnel with a 30-second keepalive — so it will look busy and will not sleep. Do not count on it. - Render and Northflank bill per running instance. Idle costs the same as busy.
- DigitalOcean, Hetzner, Vultr, Linode bill hourly for the machine. Note that powering a droplet off does not stop the bill on most of these; only destroying it does.
Size for what your agents actually do. 2 vCPU / 4 GB is a sensible floor, and a drive that clones a monorepo and runs a build wants more.
Read the service logs. The first-boot step reports exactly which stage failed.
| Log line | What to do |
|---|---|
no drive enrolled yet and COSHELL_ENROLL_TOKEN is not set |
Set the variable and redeploy. |
token is invalid, expired, or already used |
Mint a fresh one and redeploy. They last 15 minutes and burn on first use. |
could not reach ... to enroll |
The machine has no outbound HTTPS to coshell.ai. Check egress rules. |
| Enrolls, then the drive never appears in Coshell | The outbound tunnel is not connecting. Confirm outbound WebSocket traffic to coshell.ai is allowed. |
Drive shows up but with no models available: connect a provider in the Coshell dashboard. Keys are delivered to the drive over its tunnel and land in the drive's own isolated credential store, not in your personal one.
Every provider here restarts the container when it exits, so a broken enrollment would otherwise loop
fast enough to matter. The first-boot step deliberately pauses 30 seconds before giving up, which keeps
a misconfigured deploy quiet while its logs still say what to fix. COSHELL_ENROLL_BACKOFF_SECONDS
changes the pause.
docker build -t coshell-drive-deploy .
docker run --rm \
-e COSHELL_ENROLL_TOKEN=<token> \
-e COSHELL_DRIVE_NAME="Local test" \
-v coshell-test-data:/data \
coshell-drive-deployRailway's deploy button is backed by a Template, which is authored in the Railway UI rather than in a repo file. Create it once:
- Railway → workspace settings → Templates → New Template
- Add a service from this repo.
railway.jsonsupplies the Dockerfile build and an always-on restart policy. - Add a volume mounted at
/data. Railway volumes are declared on the service, not inrailway.json, so this step cannot be skipped. - Add
COSHELL_ENROLL_TOKENas a required variable andCOSHELL_DRIVE_NAMEas an optional one. - Do not generate a public domain.
- Publish the template. Railway gives you the button markdown to paste back into this README.
Questions, or a provider you want supported: coshell.ai