Post

ENG | Setting Up Claude Code in a Podman Container

Setting Up an Isolated Dev Environment with Podman and Claude Code

ENG | Setting Up Claude Code in a Podman Container

Motivation

(Dear Diary). I paid $20 to Anthropic, cause I wanted to see what are differences between free and paid tier today. Three years ago it was access to new models and early models were good mostly for fun. Difference between ChatGPT3.5 and ChatGPT 4.0 was huge. Also free models were slow. Claude Haiku was basically useless, while Opus was ok. Two years ago free models were good enough (Claude Haiku was not and Sonnet was not accessible during high load) so there was no strong motivation to pay. Limit was only low message count per time especially on Claude, but there is also ChatGPT, Qwen, Grok, …

I was surprised that Claude wrote some resource management system and asynchronous data fetch, consisting of cache, data fetch queue (load/compute), worker thread (processes queue, moves result to cache) so I decided they deserve some money and I wanted to try Claude Code anyways.

First difference I noticed is that paid Claude Opus can create multiple files and compile and run them in a container. I just tried to have conversation how to write gomoku with client-server architecture and was surprised it created whole project including make file. Only problem was that when I downloaded 10 files, directory structure was lost. But wow. This was totally unexpected.

I decided to run Claude Code in a container so it can’t access my host filesystem. Here’s how I set it up with Podman and openSUSE Leap. Maybe it’s not a typical workflow.

Side note: Last time I wanted to run something in isolation, I created “temporary” user, set weak password, forgot to disallow ssh login and many months later I found Outlaw botnet running on my mini server. Not only does it consume electricity, but it has consequences - your IP appears in various black lists, your provider and DNS servers start throttling internet traffic so significantly you will notice (also fan noise is noticeable)

Building and setting up container

First we need some distribution of our choice from Docker Hub:

I chose openSuse Leap, because I’m familiar with openSuse and Fedora and Leap has less updates. CentOS and RockyLinux do not seem to have up-to-date images on docker hub.

Preparing Dockerfile

Then we need to create Dockerfile. It may start simple:

1
FROM opensuse/leap:16.0

Now we can play with it, try installing stuff to get package names right and so on, before proceeding to creating Dockerfile which automates setup.

Useful, very basic commands are

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Build image `dev-claude-ai` from Dockerfile in the current directory
podman build -t dev-claude-ai .
# Run shell inside docker container, remove container when not in use
podman run -it --rm dev-claude-ai:latest
# Same, but as root
podman run -it --rm --user root dev-claude-ai:latest
# Create container `claude-dev` once, mount user's folder, preserve userid (both system and container have UID=1000)
# `:Z` flag is a must for SELinux, `--userns=keep-id` for UID mapping (not used in final setup)
podman run -it --name claude-dev -v ~/claude:/home/claude/projects:Z --userns=keep-id dev-claude-ai-latest
# Reuse container `claude-dev` (unlike image, container holds filesystem changes - but only until image is recreated)
podman start -ai claude-dev

# List images
podman image list
# Prune unused images
podman image prune

After some trial-error with package installation it gets a bit more complicated. The following script uses openSuse Leap 16 as a base, adds user claude, updates packages, installs python and c++ compilers, vim, ripgrep which is dependency of Claude.

Docker file syntax was new to me. Note that every run command creates ‘snapshot’ or ‘filesystem overlay’ so commands are often grouped together. There are leftovers, I did not need Python modules, later I decided to add nasm without rebuilding what was there already, so I added it as a new command. And initially I had some files in user directory already. File iteratively grew as I was trying to create variant of Gomoku (Piškvorky in Czech).

Final version just ended by creating user with user files are stored on volume. During first use, few files such as .bashrc are created on empty volume, then they are preserved. Volume stores changing and persistent data.

I have this file located in docker/claude directory together with some podman compose files and so on, which are in git repository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
FROM opensuse/leap:16.0
#FROM opensuse/leap:latest # This is 15.6
# Avoid interactive prompts
ENV ZYPP_NONINTERACTIVE=1

RUN zypper refresh && \
    zypper update -y && \
    zypper install -y python313 python313-pip clang gcc-c++ cmake make vim ripgrep git openssh gdb strace valgrind perf && \
    zypper install -y gtest benchmark-devel grpc-devel ncurses-devel SDL3-devel && \
    zypper clean -a

RUN useradd -u 1000 claude

RUN zypper install -y nasm fastfetch

#USER claude

#WORKDIR /home/claude

#RUN pip install --upgrade pip && pip install seaborn opencv-python sympy scipy

#RUN curl -fsSL https://claude.ai/install.sh | bash

Compiling and running container

Compiling docker image takes roughly 3-5 minutes on almost 10 years old mini PC with 500mbit download. This is not the first version of Dockerfile, it iteratively grew as I tried to create gomoku/piškvorky using vibe coding. Comments are left for future reference.

1
2
3
4
5
6
7
8
9
10
# Create volume for home directory
podman volume create claude-home
# Go to directory with Dockerfile
cd ~/docker/claude
# Compile image from Dockerfile in current directory
podman build -t dev-claude-ai .
# Run container `claude-dev` using image `dev-claude-ai:latest` as user, mount `claude-home` volume as home.
podman run -it --rm --name claude-dev -v claude-home:/home/claude:Z --userns=keep-id dev-claude-ai:latest
# For experiments as root
# podman run -it --rm --name claude-dev --user root dev-claude-ai:latest

Installing and running Claude in container

1
2
3
4
5
6
7
8
9
10
11
12
# Go home
cd
# Download Claude
curl -fsSL https://claude.ai/install.sh | bash
# Proceed with instructions
echo 'export PATH="$HOME/.local/bin:$PATH"' >> .bashrc
export PATH="$HOME/.local/bin:$PATH"
# Create project directory and enter it
mkdir hello_world
cd hello_world
# Run claude
claude

For experiments with containers use

1
podman run -it --rm --name claude-dev --user root dev-claude-ai:latest

And now here are some screenshots

Container preparation Container preparation

Initialization of Claude code Initialization of Claude code

Nasm Hello world Hello world in assembly

Cpp Hello world Hello world in C++

Summary

That’s it. Container works, data persists, host filesystem is safe. No regrets about the $20. I learned something new and I haven’t had any surprise botnets this time (yet).

Bad news is that in two evenings, I’ve spent 3/4 of my weekly usage limit.

This post is licensed under CC BY 4.0 by the author.