ASMY Services ASMY SERVICES
← Back to news 25 September 2026

Keeping Claude Code running on a Linux server, around the clock

I spend a fair bit of time away from a desk, but the servers I look after don’t stop needing attention just because I’m not sitting in front of them. For a while now I’ve had Claude Code running permanently on one of my Linux boxes. From the Claude app on my phone, or from claude.ai/code in a browser, I can open a session and it’s working on that server with my projects already there.

It runs inside a Docker container that can only see the folders I’ve deliberately handed to it. When it needs something from the server itself, like restarting a service or reading its logs, it has to ask, and it can only ask for things I’ve put on a list. I’ve packaged the whole setup up as claude-sandbox on GitHub. This post covers how it fits together, how to get it running, and the bits that caught me out.

How it fits together

  • The container runs claude remote-control, which registers the server with your Claude account under a name you pick. When you open a session from the app or the web, Claude works inside the container, in /workspace. It can see that folder, its own home directory (login and settings) and any host folders you choose to mount. Nothing else from the server.
  • The host is reached through a queue, not a shell. Inside the container, Claude runs something like hostctl restart service=my-bot. That drops a small request file into a queue folder.
  • A broker on the host picks up the request, checks it against the list of actions in config.yaml, and runs the matching fixed command. No shell is involved, and arguments can only be values from lists or number ranges you’ve defined. The result goes back to Claude.
  • Risky actions wait for you. Anything marked approval: true, like deploying code or replacing a crontab, sits there until you approve or deny it. It can ping you on Discord when one arrives.
  • Everything is logged. Every request, approved or not, goes into an audit log on the host.

Docker’s restart policy brings the container back if it falls over, and a pair of systemd user units run the broker whenever a request turns up.

A word of caution first

Be clear about what you’re setting up here. This gives an AI agent a standing way into your server. The container and the action list are a big part of why I’m comfortable with that, but they only limit what you let them.

Some things I’d treat as non-negotiable:

  • Only mount what it actually needs. Don’t mount a code base in “just in case”, and never mount your whole home directory or ~/.ssh. Mounts are read-only unless you say otherwise, and that’s how most of them should stay.
  • Code the host runs is still a way in. If you mount a folder that a service or cron job runs code from as writable, Claude can effectively run code on the host by editing it. Have it work on its own clones in /workspace, push its changes, and pull them into the live copy with a deploy action that needs your approval.
  • Secrets in mounted folders are visible. If a mounted folder has .env files or tokens in it, Claude can read them.
  • Keep the action list short. Every action is something the agent can do on the host. Start with status and logs, add more as you actually need them, and put approval: true on anything that changes things.
  • Docker access is root access. Anyone in the docker group is effectively root on the host. Rootless Docker avoids that, and it’s what I’d recommend. The repo’s README walks through setting it up.
  • Remember it’s still on your network. The container can reach the internet and anything else your server can reach on the local network. If that matters in your setup, restrict it at the firewall.
  • Set boundaries in your prompts. Setup puts a CLAUDE.md in /workspace explaining how the sandbox works. Add your own rules to it: what’s off limits, and that it should stop and check with you before anything destructive.
  • Don’t treat those instructions as the only safeguard. AI agents are good at getting a job done, and occasionally that means working around a restriction you assumed would hold, or going somewhere you didn’t expect. Prompts reduce the chance of that. The container, read-only mounts, the action list, approvals, git and backups are what actually stop the damage when it happens.

By default, Claude doesn’t stop to ask permission before each command inside the container, because the container is the boundary. That’s exactly why the points above matter. If you’d rather approve commands as it goes, set permission_mode: default in the config. Either way, the agent can’t change that setting itself: it’s mounted read-only.

None of this is a reason not to do it. I find it genuinely useful. Just set it up the way you’d set up access for any person you haven’t worked with long: enough to do the job, and no more.

What you need

  • A Linux server with systemd, where your user can run user services (systemctl --user)
  • Docker with the Compose plugin, ideally rootless
  • Python 3.8 or newer with PyYAML (apt install python3-yaml)
  • A Claude account that can use Claude Code

Getting it running

git clone https://github.com/asmyservices/claude-sandbox ~/claude-sandbox
cd ~/claude-sandbox
cp config.example.yaml config.yaml
nano config.yaml          # at least the name, and your services and projects
./sandbox check           # validate the config
./sandbox setup           # data folders, broker units, build the image
./sandbox login           # one-time, see below
./sandbox up              # start it
loginctl enable-linger    # keep it all running after you log out

./sandbox login opens an interactive Claude session inside the container. In it:

  1. Log in with your Claude account, and say yes when it asks whether to trust /workspace.
  2. Run /remote-control once and answer y to enable it.
  3. Run /quit.

The container normally runs without a terminal, so it can’t answer those prompts on its own. Doing them once here saves the answers, and they survive rebuilds.

After ./sandbox up, your server shows up in the Claude app and on claude.ai/code under the name you gave it. Pick it and start a session.

The config

Everything lives in config.yaml, and the example file is fully commented. The parts you’ll touch most:

name: my-server        # what shows up in the app
model: opus            # optional default model for new sessions

mounts:
  - host: ~/logs
    container: /mnt/logs
    mode: ro

actions:
  logs:
    description: Show a service's recent log lines.
    command: [journalctl, --user, -u, "{service}", -n, "{lines}", --no-pager]
    args:
      service: {choices: [my-bot, my-site]}
      lines: {integer: {min: 1, max: 1000}, default: 100}

  restart:
    description: Restart a service.
    command: [systemctl, --user, restart, "{service}"]
    args:
      service: {choices: [my-bot, my-site]}

  deploy:
    description: Pull the latest pushed code into a project's live copy.
    command: [git, -C, "{home}/{project}", pull, --ff-only]
    args:
      project: {choices: [my-bot]}
    approval: true

For the model, an alias like opus or sonnet follows the latest version, or you can put in a full model ID to pin one. ./sandbox check catches typos, unknown keys and anything that would let free text reach a command line, so run it after every edit.

Approving requests

When Claude asks for something that needs approval, it tells you the request id and carries on. From the server:

./sandbox pending          # what's waiting
./sandbox show <id>        # the full request, including any file content
./sandbox approve <id>     # run it and send the result back
./sandbox deny <id> --reason "not right now, wait until tonight"

Requests are checked against the current config again when you approve them, so if you’ve removed an action in the meantime, anything still waiting for it is blocked too.

Day to day

./sandbox status           # container, broker, and anything waiting on you
./sandbox logs -n 50       # recent container logs
./sandbox upgrade          # rebuild with the newest Claude Code and restart
./sandbox restart          # recreate the container
./sandbox shell            # a shell inside the container, for poking around

Things that caught me out

Upgrading or restarting ends every open session. That includes the one you’re typing in. The app reconnects and the conversation carries on, but whatever command was running at the time is gone. Claude can’t do this from inside the container anyway, which is how it should be. I run it from a normal SSH login, when nothing important is mid-flight.

Don’t forget /remote-control during login. If the server never appears in the app, it’s almost always because that step was skipped. Run ./sandbox login again, do all three steps, then ./sandbox restart.

Updating from inside the container doesn’t stick. The image is rebuilt from scratch, so running claude update inside it is lost the next time it rebuilds. Use ./sandbox upgrade, or pin claude_code_version in the config if you’d rather upgrade on purpose.

Mounts, not symlinks. A symlink inside a mounted folder that points somewhere outside it won’t work in the container, because the target isn’t there. If Claude needs something, mount it.

File ownership on rootless Docker. If files in the data folders end up owned by an odd user ID, set container.user explicitly. On rootless Docker it should be 0:0, which maps back to your own user on the host.

Resource limits on older systems. Rootless Docker can only enforce memory and CPU limits on cgroup v2. If docker info shows cgroup version 1, take container.memory and container.cpus out of the config. The README covers limiting the whole user account instead.

The code, the full config reference and the security details are all in the claude-sandbox repository. If you’d like a hand setting something like this up on your own servers, get in touch.