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:
- Run Claude Code in a Docker container
- Install ShellHub agent in the container
- 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"]
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
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
Running It
Build the image:
docker build -t shellhub-claude-code .
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
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 yourshellhub.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:
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
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:
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)