Skip to content

Instantly share code, notes, and snippets.

@LeoVS09
Last active March 27, 2026 15:59
Show Gist options
  • Select an option

  • Save LeoVS09/20f7feab7e698311dee88718155988d2 to your computer and use it in GitHub Desktop.

Select an option

Save LeoVS09/20f7feab7e698311dee88718155988d2 to your computer and use it in GitHub Desktop.
Dev Container for Claude Code

Development

Claude Code Dev Container

This project uses Dev Containers, which included in VS Code, Cursor, Antigravity. You can use bottom left corner button to open Dev Container view, and then click on "Reopen in Container" button to open the project in a container.

Or you can use cli. In order to activate it, run following in your IDE:

Press cmd/ctrl+shift+p or F1 and select the Dev Containers: Install devcontainer CLI command to install it.

Then you can run the following command to open the project in a container:

devcontainer up && devcontainer exec --workspace-folder . bash

To attach new console to container use this commands:

docker ps
docker exec -it -u node <container_id> zsh
#!/usr/bin/env bash
set -euo pipefail
echo "🔧 Configuring Claude Code environment..."
# -- Copy statusline script to ~/.claude --
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p ~/.claude
cp "$SCRIPT_DIR/statusline.sh" ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.sh
echo "✅ Copied statusline.sh to ~/.claude/"
if [ ! -f ~/.claude/settings.json ]; then
echo "🔧 Creating ~/.claude/settings.json..."
cat > ~/.claude/settings.json << 'EOF'
{
"permissions": {
"defaultMode": "auto"
},
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'codemap . && git diff:' && git diff --stat && echo 'git status:' && git status"
}
]
}
]
},
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline.sh"
},
"alwaysThinkingEnabled": true,
"skipDangerousModePermissionPrompt": true,
"effortLevel": "high",
"autoUpdatesChannel": "stable",
"companyAnnouncements": ["Thank you for using NeoLab Dev Container Sandbox", "Happy engineering!"]
}
EOF
echo "✅ Created ~/.claude/settings.json with bypass permissions."
fi
if [ ! -f ~/.claude.json ]; then
echo "🔧 Creating ~/.claude.json..."
cat > ~/.claude.json << 'EOF'
{
"autoCompactEnabled": false
}
EOF
echo "✅ Created ~/.claude.json with autoCompactEnabled set to false."
fi
retry() {
local max_attempts="${RETRY_ATTEMPTS:-3}"
local delay="${RETRY_DELAY:-1}"
local attempt=1
while [ "$attempt" -le "$max_attempts" ]; do
if "$@"; then
return 0
fi
echo "⚠️ Attempt $attempt/$max_attempts failed: $*"
if [ "$attempt" -lt "$max_attempts" ]; then
echo " Retrying in ${delay}s..."
sleep "$delay"
fi
attempt=$((attempt + 1))
done
echo "❌ All $max_attempts attempts failed: $*"
return 1
}
# echo "🔧 Installing typescript lsp..."
retry claude plugin marketplace add anthropics/claude-plugins-official
retry claude plugin install typescript-lsp@claude-plugins-official
# echo "🔧 Installing context-engineering-kit plugins..."
retry claude plugin marketplace add NeoLabHQ/context-engineering-kit
retry claude plugin install sdd@context-engineering-kit
retry claude plugin install sadd@context-engineering-kit
retry claude plugin install git@context-engineering-kit
retry claude plugin install ddd@context-engineering-kit
retry claude plugin install code-review@context-engineering-kit
echo "🚀 Claude Code environment ready."
echo "Use 'claude' to run Claude Code"
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "Node 22 (bookworm) + Claude Code",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"build": {
"dockerfile": "Dockerfile"
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/devcontainers-extra/features/bash-command:1": {
// Install docker mcp
"command": "git clone https://github.com/docker/mcp-gateway.git /tmp/mcp-gateway && cd /tmp/mcp-gateway && mkdir -p '/home/node/.docker/cli-plugins/' && DOCKER_MCP_CLI_PLUGIN_DST=/home/node/.docker/cli-plugins/docker-mcp && HOME=/home/node && make docker-mcp && chown -R node:node /home/node/"
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh"
},
"extensions": [
"streetsidesoftware.code-spell-checker",
"anthropic.claude-code",
"dbaeumer.vscode-eslint",
"redhat.vscode-yaml",
"redhat.vscode-xml",
"redhat.vscode-json"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000, 8080],
// Use 'portsAttributes' to set default properties for specific forwarded ports.
// More info: https://containers.dev/implementors/json_reference/#port-attributes
"portsAttributes": {
"3000": {
"label": "Dev Server",
"onAutoForward": "notify"
}
},
// -- Environment variables --
"containerEnv": {
"NODE_ENV": "development",
"COLORTERM": "truecolor"
},
"remoteEnv": {
// if anthropic api key is set, will skip login
"ANTHROPIC_API_KEY": "${localEnv:ANTHROPIC_API_KEY}",
"CONTEXT7_API_KEY": "${localEnv:CONTEXT7_API_KEY}"
},
// Install MCP servers and plugins after the container starts (needs runtime env vars)
"postCreateCommand": {
"install-mcps": "/opt/devcontainer/install-mcps.sh"
},
"remoteUser": "node"
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
##############################################
# Stage 1: Base image with all tool installs
##############################################
FROM mcr.microsoft.com/devcontainers/javascript-node:24-bullseye AS base
# Install common utils
RUN apt-get update && apt-get install -y \
apt-utils \
bash-completion \
openssh-client \
gnupg2 \
dirmngr \
iproute2 \
procps \
lsof \
htop \
net-tools \
psmisc \
curl \
tree \
wget \
rsync \
ca-certificates \
unzip \
bzip2 \
xz-utils \
zip \
nano \
vim-tiny \
less \
jq \
lsb-release \
apt-transport-https \
dialog \
libc6 \
libgcc1 \
libkrb5-3 \
libgssapi-krb5-2 \
libicu[0-9][0-9] \
liblttng-ust[0-9] \
libstdc++6 \
zlib1g \
locales \
sudo \
ncdu \
man-db \
strace \
manpages \
manpages-dev \
init-system-helpers \
build-essential \
procps \
file \
curl \
git
# Upgrade packages and remove unused dependencies
RUN apt-get -y upgrade --no-install-recommends && apt-get autoremove -y
# Install Github CLI
RUN (type -p wget >/dev/null || (sudo apt update && sudo apt install wget -y)) \
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& sudo mkdir -p -m 755 /etc/apt/sources.list.d \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& sudo apt update \
&& sudo apt install gh -y
# Install Golang
RUN curl -fsSL https://go.dev/dl/go1.26.1.linux-amd64.tar.gz | tar -C /usr/local -xz
ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}"
# Install Codemap
RUN git clone --depth 1 https://github.com/JordanCoin/codemap.git /tmp/codemap \
&& cd /tmp/codemap && /usr/local/go/bin/go build -o /usr/local/bin/codemap . \
&& rm -rf /tmp/codemap
##############################################
# Stage 2: Install non-root tools
##############################################
FROM base AS tools
USER node
# Install Claude Code
RUN curl -fsSL https://claude.ai/install.sh | bash
# Need for claude code to work properly
ENV PATH="/home/node/.local/bin:${PATH}"
RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
ENV PATH="/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH}"
# Install TypeScript LSP and Additional Tools
RUN npm install -g typescript-language-server typescript rust-just bun
##############################################
# Stage 3: Prepare scripts
##############################################
FROM tools AS scripts
USER root
COPY configure-claude.sh statusline.sh install-mcps.sh /opt/devcontainer/
RUN chmod +x /opt/devcontainer/*.sh
##############################################
# Stage 4: Configure Claude Code
##############################################
FROM scripts
USER node
ENV DOCKER_MCP_IN_CONTAINER=1
RUN /opt/devcontainer/configure-claude.sh
#!/usr/bin/env bash
set -euo pipefail
echo "🔧 Adding Context7 MCP server..."
claude mcp add --scope user --transport http context7 https://mcp.context7.com/mcp --header "CONTEXT7_API_KEY: ${CONTEXT7_API_KEY:-}"
# echo "🔧 Adding Docker MCP servers..."
# docker mcp feature enable profiles
# docker mcp catalog pull mcp/docker-mcp-catalog
# docker mcp profile create --name dev-tools --server catalog://mcp/docker-mcp-catalog/paper-search --connect claude-code
echo "✅ MCP servers configured."
#!/bin/bash
input=$(cat)
# === ORIGINAL FIRST LINE (with 2 text substitutions) ===
model=$(echo "$input" | jq -r '.model.display_name // .model.id')
style=$(echo "$input" | jq -r '.output_style.name // "default"')
total_input=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0')
total_output=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0')
context_size=$(echo "$input" | jq -r '.context_window.context_window_size // 0')
total_used=$((total_input + total_output))
if [ "$context_size" -gt 0 ]; then
percentage=$(awk "BEGIN {printf \"%.1f\", ($total_used / $context_size) * 100}")
else
percentage="0.0"
fi
if [ "$total_used" -ge 1000 ]; then
used_display=$(awk "BEGIN {printf \"%.1fK\", $total_used / 1000}")
else
used_display="${total_used}"
fi
if [ "$context_size" -ge 1000 ]; then
size_display=$(awk "BEGIN {printf \"%.0fK\", $context_size / 1000}")
else
size_display="${context_size}"
fi
git_branch=$(git -c core.fileMode=false config advice.detachedHead false 2>/dev/null && git branch --show-current 2>/dev/null || echo "no-git")
# Session duration
duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
duration_sec=$((duration_ms / 1000))
dur_mins=$((duration_sec / 60))
dur_secs=$((duration_sec % 60))
session_dur="${dur_mins}m ${dur_secs}s"
printf '\033[01;36m%s\033[00m \033[90m|\033[00m \033[01;33mStyle: %s\033[00m \033[90m|\033[00m \033[01;35m%s/%s (%s%%)\033[00m \033[90m|\033[00m \033[01;32m%s\033[00m \033[90m|\033[00m \033[2;37mEsc: interrupt\033[00m \033[90m|\033[00m \033[2;37mShift + Enter: new line\033[00m \033[90m|\033[00m \033[2;37m⏱️ %s\033[00m\n' "$model" "$style" "$used_display" "$size_display" "$percentage" "$git_branch" "$session_dur"
# === Change 3: Add current/weekly usage lines ===
dot_bar() {
local p=$1 t=10 b="" i
local f=$(((p*t+50)/100))
for i in $(seq 1 $t); do
if [ $i -le $f ]; then b="${b}● "; else b="${b}○ "; fi
done
printf "%s" "${b% }"
}
fmt_dur() {
local s=$1
if [ $s -lt 60 ]; then echo "${s}s"
elif [ $s -lt 3600 ]; then echo "$((s/60))min"
else local h=$((s/3600)) m=$(((s%3600)/60)); [ $m -eq 0 ] && echo "${h}hr" || echo "${h}hr ${m}min"
fi
}
# Session line - uses ccusage blocks --active for real data
block_json=$(npx -y ccusage blocks --json --active 2>/dev/null)
block_cost=$(echo "$block_json" | jq -r '.blocks[0].costUSD // 0')
block_remaining=$(echo "$block_json" | jq -r '.blocks[0].projection.remainingMinutes // 0')
block_total_cost=$(echo "$block_json" | jq -r '.blocks[0].projection.totalCost // 0')
# Calculate block usage percentage (projected cost out of a 5hr block)
# Use actual cost vs projected total as progress indicator
if [ "$block_total_cost" != "0" ] && [ "$block_total_cost" != "null" ]; then
block_pct=$(awk "BEGIN {v=($block_cost / $block_total_cost) * 100; printf \"%.0f\", v}")
else
block_pct=0
fi
[ "$block_pct" -gt 100 ] 2>/dev/null && block_pct=100
cur_bar=$(dot_bar "$block_pct")
# Format remaining time
remaining_int=$(printf "%.0f" "$block_remaining")
if [ "$remaining_int" -ge 60 ]; then
rem_h=$((remaining_int / 60))
rem_m=$((remaining_int % 60))
[ $rem_m -eq 0 ] && rem_display="${rem_h}hr" || rem_display="${rem_h}hr ${rem_m}min"
else
rem_display="${remaining_int}min"
fi
# Format cost
block_cost_fmt=$(printf '$%.2f' "$block_cost")
printf 'session %s %d%% ↻ %s till refresh\n' "$cur_bar" "$block_pct" "$rem_display"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment