DEV Community

Luis Gustavo S. Barreto
Luis Gustavo S. Barreto

Posted on

Remote AI Coding with Claude Code and ShellHub

In the rapidly evolving world of AI-assisted development, having access to powerful AI coding assistants from anywhere has become essential.

I've been a big fan of Claude Code since it launched. Having an AI assistant that can understand large codebases, make thoughtful edits, and actually reason about architecture has been game-changing for my workflow.

But what if you want to access Claude Code remotely, from any device?

When I started a conversation on my laptop, that context stayed there. If I wanted to continue from my desktop, I'd have to re-explain everything. And if I was on my tablet or phone? Forget about it. I needed a way to have my Claude Code environment accessible from anywhere, without losing context or messing with complex networking.

That's when I realized: what if I just containerized Claude Code and made it accessible via SSH through ShellHub?

The Problem

Here's the thing—Claude Code runs locally on your machine. That's great for privacy and speed, but terrible for mobility.

I could have set up a VM in the cloud, but then I'm paying for compute even when I'm not using it. Plus, I wanted to keep my development close to home—sometimes I need access to local hardware, Docker on my machine, or files that shouldn't leave my network.

The Solution: ShellHub + Docker

ShellHub is this cool open-source project that makes SSH access dead simple. No port forwarding, no VPN setup, no dynamic DNS headaches. Devices register with a ShellHub server and become instantly accessible via SSH from anywhere. It's like Tailscale, but specifically designed for SSH access, and you can self-host the server.

The idea is simple:

  1. Run Claude Code in a Docker container
  2. Install ShellHub agent in the container
  3. SSH in from any device and land directly in Claude Code

And since everything runs in Docker, the workspace and Claude's context persist. Start a session on your laptop, continue from your phone using Termius—it all just works.

Setup

The setup is surprisingly straightforward. Here's what we're building:

  • Base: Alpine Linux (small, fast)
  • Claude Code: Installed via npm
  • ShellHub Agent: Downloaded from GitHub releases
  • User shell: Bash configured to auto-launch Claude Code
  • Workspace: Persistent directory for your code

The clever bit is in how we handle the shell. When you SSH in, ShellHub runs bash --login. We configure the ~/.profile to automatically exec into Claude Code for interactive SSH sessions.

This means:

  • SSH in → bash loads → .profile runs → Claude Code launches
  • You land directly in Claude Code without typing any commands
  • Non-interactive commands (like scp) work normally

Let's build it.

The Dockerfile

I wanted to keep this lean. Alpine Linux as the base gives us a tiny image (~200MB total), and we only install what we need:

FROM alpine:3.19

# Install dependencies
RUN apk add --no-cache \
    nodejs \
    npm \
    bash \
    curl \
    ca-certificates \
    git \
    openssh-client \
    sudo

# Install Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code

# Download and install ShellHub agent binary
RUN curl -L https://github.com/shellhub-io/shellhub/releases/download/v0.21.0-rc.2/shellhub-agent-linux-amd64.gz -o /tmp/shellhub-agent.gz && \
    gunzip /tmp/shellhub-agent.gz && \
    mv /tmp/shellhub-agent /usr/local/bin/shellhub-agent && \
    chmod +x /usr/local/bin/shellhub-agent

# Create claudeuser with bash as shell and home directory
RUN adduser -D -s /bin/bash -h /home/claudeuser claudeuser && \
    echo "claudeuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Create workspace directory inside user home
RUN mkdir -p /home/claudeuser/workspace && \
    chown -R claudeuser:claudeuser /home/claudeuser

# Copy profile configuration
COPY profile /home/claudeuser/.profile
RUN chown claudeuser:claudeuser /home/claudeuser/.profile

# Copy entrypoint script
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
Enter fullscreen mode Exit fullscreen mode

A few notes on design choices:

  • I download the ShellHub agent binary directly from GitHub releases
  • The user gets /bin/bash as their shell in /etc/passwd. I initially tried setting the shell to /usr/local/bin/claude directly, but ShellHub's --login flag doesn't play nice with non-standard shells
  • Workspace lives in the user's home directory (/home/claudeuser/workspace), which makes permissions straightforward

The Profile Magic

Here's where it gets interesting. The .profile file is what actually launches Claude:

#!/bin/bash

# Set working directory to workspace
cd ~/workspace 2>/dev/null || cd ~

# Check if this is an interactive shell
if [[ $- == *i* ]]; then
    # Launch Claude Code for interactive sessions
    exec /usr/local/bin/claude
fi
Enter fullscreen mode Exit fullscreen mode

The $- variable contains shell flags. When i is present, you've got an interactive shell. The exec is key—it replaces the bash process with Claude Code, so when you exit Claude, the SSH session terminates cleanly.

Why check for interactive? Because scp, automated scripts, and other non-interactive SSH commands need to work. Without this check, trying to scp a file would launch Claude Code instead. Not ideal.

The Entrypoint

The entrypoint starts the ShellHub agent:

#!/bin/bash

# Configure ShellHub agent (agent reads from env vars directly)
if [ -z "$SHELLHUB_TENANT_ID" ]; then
    echo "ERROR: SHELLHUB_TENANT_ID environment variable is required"
    exit 1
fi

if [ -z "$SHELLHUB_SERVER_ADDRESS" ]; then
    echo "ERROR: SHELLHUB_SERVER_ADDRESS environment variable is required"
    exit 1
fi

# Start ShellHub agent in background (configured via env vars)
echo "Starting ShellHub agent..."
/usr/local/bin/shellhub-agent &

AGENT_PID=$!

echo "ShellHub agent started (PID: $AGENT_PID)"
echo "Claude Code shell ready for connections"
echo "Workspace: /home/claudeuser/workspace"

# Keep container running and monitor agent
wait $AGENT_PID
Enter fullscreen mode Exit fullscreen mode

Running It

Build the image:

docker build -t shellhub-claude-code .
Enter fullscreen mode Exit fullscreen mode

Run the container (adjust the values for your setup):

docker run --rm \
  -e SHELLHUB_TENANT_ID=00000000-0000-4000-0000-000000000000 \  # Your tenant ID from ShellHub
  -e SHELLHUB_SERVER_ADDRESS=http://172.17.0.1 \                # Your ShellHub server address
  -e SHELLHUB_PRIVATE_KEY=/etc/shellhub/shellhub.key \          # Path inside container to key
  -e SHELLHUB_PREFERRED_HOSTNAME=claudecode \                   # Device name in ShellHub
  -v ./keys:/etc/shellhub \                                      # Mount local keys directory
  -v ./workspace:/home/claudeuser/workspace \                    # Mount local workspace
  shellhub-claude-code
Enter fullscreen mode Exit fullscreen mode

You'll need to:

  • Replace SHELLHUB_TENANT_ID with your tenant ID from the ShellHub dashboard
  • Replace SHELLHUB_SERVER_ADDRESS with your ShellHub server URL
  • Create a ./keys directory with your shellhub.key file (if using device authentication)
  • Create a ./workspace directory for your code files

The container starts, the ShellHub agent registers with your server, and the device shows up in your dashboard. Accept it, and you're good to go.

Connecting

Once your device is registered and accepted in ShellHub, you'll see it in the dashboard with connection details:

ShellHub Connection Interface

You can connect in two ways:

Option 1: Through the ShellHub UI
Click the terminal icon in the dashboard (as shown above) to open a web-based SSH session directly in your browser.

Option 2: Using your SSH client

ssh claudeuser@namespace.claudecode@your-shellhub-server
Enter fullscreen mode Exit fullscreen mode

Either way, you land directly in Claude Code. No commands to run, no setup.

First time connecting? Claude Code will automatically prompt you to authenticate. It'll generate a link for you to visit—just open it, get your authentication token, and paste it back into Claude Code. That's it. From now on, you've got Claude available from anywhere.

And just like that, you're in:

Claude Code Ready

I can start a conversation on my laptop, SSH in from my phone on Termius during my commute, and pick up exactly where I left off. The workspace persists, Claude's context persists—it all just works.

Why This Works

It works everywhere. Your container can be behind NAT, on a residential IP, wherever. ShellHub handles the networking. No port forwarding, no dynamic DNS, no VPN.

Any device with SSH. Laptop, desktop, tablet, phone. Even a friend's computer if you trust them with your SSH key. ShellHub only needs a standard SSH client.

Context persists. Unlike starting fresh each time, your Claude conversations and workspace state survive between sessions.

It's yours. Self-hosted, open source, no vendor lock-in. You control where it runs and who has access.

Things to Know

Single user: This setup is for one person at a time. Multiple SSH sessions will fight over the same Claude instance. For teams, spin up separate containers per user.

API keys: Claude stores your API key in /home/claudeuser. If you don't persist that directory with a volume, you'll need to /login again after container restarts.

Why Not Just Use...

Tailscale? Tailscale requires installing software on every device you want to access from. ShellHub just needs SSH—works great on shared computers or locked-down environments.

Direct SSH? Sure, if you want to deal with port forwarding, dynamic DNS, or paying for a static IP. ShellHub makes all that disappear.

Final Thoughts

This setup has become part of my daily workflow. I've got Claude accessible from my home lab, reachable from anywhere. No friction, no complexity, just works.

The whole thing is maybe 100 lines of code across a Dockerfile and shell scripts. But it solves a real problem: making AI-assisted development truly portable.

If you've been wanting remote access to Claude Code without cloud vendor lock-in, give this a try.

Top comments (0)