A containerized development environment with transparent URL-allowlisting proxy.
This guide assumes a Linux development environment, either local or accessed via SSH.
The goal is to provide security boundaries around file access while maintaining a mostly seamless development experience. The container should feel like a transparent pass-through of the host environment—matching paths, toolchains, and workflows—while isolating sensitive areas outside the designated workspace. This is useful for running AI coding assistants (Claude, Codex, pi, etc.) with controlled file and network access. Credentials can be kept on the host and injected via proxy, so tokens never exist inside the container.
This is an opinionated setup using host bind mounts for a pass-through experience. It can be adapted to your needs—see docs/SELF-CONTAINED.md for a traditional self-contained container approach.
Security note: The workspace directory is intended for development where content is acceptable to share with an LLM or between LLM sessions. Personal sensitive files should not be placed in the workspace directory, as it is fully accessible from within the container.
This repo provides scripts and configuration to run your development work inside a Podman container with:
- Minimal image - Uses host toolchains via bind mounts
- Network control - Optional URL allowlisting via iptables and acl-proxy
- Worktree workflow - Git worktree management with
wt(worktrunk) - Path consistency - Same paths work on host and in container (
~/worktrees/project) - State preservation - AI assistant configs, cargo/rustup, etc. persist between sessions
- Linux host (tested on Rocky Linux)
- Rootless Podman (can be adapted for Docker)
- Rust toolchain (for building acl-proxy and worktrunk)
- sudo access (for iptables setup via nsenter)
If SELinux is in enforcing mode, containers may be blocked from reading certificate files. After starting the container and seeing permission errors, create a policy module to allow access:
# Generate and install policy from recent denials
sudo ausearch -m avc -ts recent | audit2allow -M container_certs
sudo semodule -i container_certs.ppcargo install worktrunkThen create the config file:
mkdir -p ~/.config/worktrunk
cat > ~/.config/worktrunk/config.toml << 'EOF'
# Worktree path template
# Creates: repo/branch as siblings
worktree-path = "../{{ branch | sanitize }}"
EOFgit clone https://github.com/kcosr/devtools.git ~/devtools
cd ~/devtools./container/build.shmkdir -p workspace/worktrees
mkdir -p ~/worktrees
# Add bind mount to /etc/fstab (requires sudo)
# Replace $USER with your actual username
echo "/home/$USER/devtools/workspace/worktrees /home/$USER/worktrees none bind 0 0" | sudo tee -a /etc/fstab
# Mount it now (mount looks up target path in fstab)
sudo mount /home/$USER/worktreesWhy bind mount instead of symlink? Git worktree stores absolute paths in .git metadata. A symlink would be resolved to the real path (e.g., ~/devtools/workspace/worktrees/...), breaking path consistency. A bind mount makes both paths identical at the kernel level, so git stores ~/worktrees/... which works in both host and container.
# Copy completion scripts
mkdir -p ~/.bash_completion.d
cp host/bash_completion.d/*.bash ~/.bash_completion.d/
# Add devtools integration to your bashrc
cat host/bashrc.additions >> ~/.bashrc
source ~/.bashrc| Command | Description |
|---|---|
c [dir] |
Launch container. Optional dir starts shell in that worktree. |
w [dir] |
Navigate to workspace. w = workspace root, w myproject = worktree. |
wt |
Worktrunk integration (alias override for wt clone around git clone). wt clone <url> creates a bare repo for worktrees. |
y |
Copy stdin to clipboard via OSC 52. Usage: cat file | y |
Tab completion works for c and w (completes worktree directories).
Tip: The
wcommand is useful for running dev servers from the host after agents build inside the container. For example:w myproject && npm run startorw myproject/feature && cargo run. Since paths are consistent between host and container, you can build in the container and run on the host without copying files.
c # using the alias
# or
./container/run.sh # directlyc # launch shell in workspace root
c myproject/main # launch in specific worktree| Variable | Default | Description |
|---|---|---|
PROXY |
0 | Enable transparent proxy (PROXY=1) |
PERSISTENT |
0 | Keep container running between sessions |
CONTAINER_NAME |
random | Set a specific container name |
# Ephemeral container (removed on exit)
./container/run.sh
# Persistent container
PERSISTENT=1 CONTAINER_NAME=dev ./container/run.sh
# With transparent proxy
PROXY=1 ./container/run.sh
# Run a command and exit
./container/run.sh cargo build --releasePersistent containers keep running until you stop them:
podman stop devcontainerTo remove a stopped container:
podman rm devcontainerThe proxy is an optional add-on for controlled environments. It runs on the host and the container routes outbound HTTP/HTTPS through it. It is experimental, but it has worked well for the current developer flows using Claude, Codex, and pi. Contributions are welcome, especially from folks with Rust and HTTP/2 expertise.
# Terminal 1: Start proxy
acl-proxy --config ~/devtools/proxy/acl-proxy.toml
# Terminal 2: Launch container with proxy
PROXY=1 cSee docs/PROXY.md for full setup (CA trust, allowlist rules, GitHub auth injection, troubleshooting).
Files in container/overlay/ are mounted into the container, overriding defaults:
Rename the placeholder home directory to match your username before running:
mv container/overlay/home/user container/overlay/home/$USERoverlay/home/<username>/.bashrc- Shell customizationoverlay/home/<username>/.gitconfig- Set your name/email, or bind mount your existing.gitconfiginrun.shoverlay/home/<username>/.config/gh/- GitHub CLI config
The container mounts /usr, /lib, and select /etc paths (certificates, resolv.conf, passwd, etc.) from the host. Install tools on the host and they're available in the container.
For container-specific tools, see docs/SELF-CONTAINED.md.
devtools/
├── container/
│ ├── Dockerfile # Minimal image definition
│ ├── build.sh # Build script
│ ├── run.sh # Container launcher
│ └── overlay/ # Config file overrides
│ └── home/user/ # User config overrides (rename to your username)
├── workspace/ # Your $HOME inside the container
│ └── worktrees/ # Git worktrees
├── proxy/
│ ├── acl-proxy.toml # URL allowlist config
├── host/
│ ├── bashrc.additions # Add to ~/.bashrc
│ └── bash_completion.d/
└── docs/
├── ARCHITECTURE.md # How it works
├── PROXY.md # Proxy details
└── SELF-CONTAINED.md # Full Dockerfile option
- Architecture - How the container environment works
- Proxy Add-on - Transparent proxy configuration
- Self-Contained Build - Alternative full Dockerfile
MIT