#!/bin/bash set -euo pipefail # Script to run Claude Code in a secure devcontainer # Usage: ./run-container.sh "your prompt here" # Or with stdin: echo "your prompt" | ./run-container.sh IMAGE_NAME="claude-dev-container" CONTAINER_NAME="claude-dev-container-$$" VOLUME_HISTORY="claude-code-bashhistory" VOLUME_CONFIG="claude-code-config" # Colors for output GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' # No Color # Function to print colored messages log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Check if Docker is available if ! command -v docker &> /dev/null; then log_error "Docker is not installed or not in PATH" exit 1 fi # Build image if it doesn't exist if ! docker image inspect "$IMAGE_NAME" &> /dev/null; then log_info "Image '$IMAGE_NAME' not found. Building..." docker build -t "$IMAGE_NAME" .devcontainer log_info "Image built successfully" else log_info "Using existing image '$IMAGE_NAME'" fi # Create volumes if they don't exist if ! docker volume inspect "$VOLUME_HISTORY" &> /dev/null; then log_info "Creating volume '$VOLUME_HISTORY'" docker volume create "$VOLUME_HISTORY" > /dev/null fi if ! docker volume inspect "$VOLUME_CONFIG" &> /dev/null; then log_info "Creating volume '$VOLUME_CONFIG'" docker volume create "$VOLUME_CONFIG" > /dev/null fi # Get the prompt from arguments or stdin PROMPT="" if [ $# -gt 0 ]; then # Use arguments as prompt PROMPT="$*" elif [ ! -t 0 ]; then # Read from stdin if available PROMPT=$(cat) fi # Check if running in interactive mode (no prompt provided) if [ -z "$PROMPT" ]; then log_info "No prompt provided. Starting interactive mode..." log_info "You can now run 'claude' to authenticate or use Claude Code interactively" # Run the container in interactive mode docker run --rm -it \ --name "$CONTAINER_NAME" \ --cap-add=NET_ADMIN \ --cap-add=NET_RAW \ -v "$(pwd):/workspace" \ -v "$VOLUME_HISTORY:/commandhistory" \ -v "$VOLUME_CONFIG:/home/node/.claude" \ -e NODE_OPTIONS="--max-old-space-size=4096" \ -e CLAUDE_CONFIG_DIR="/home/node/.claude" \ -e POWERLEVEL9K_DISABLE_GITSTATUS="true" \ -w /workspace \ --user node \ "$IMAGE_NAME" \ /bin/bash -c " # Initialize firewall echo 'Initializing firewall...' sudo /usr/local/bin/init-firewall.sh echo '' echo 'Container ready! Firewall initialized.' echo 'Run \"claude\" to authenticate or use Claude Code interactively.' echo 'Type \"exit\" to leave the container.' echo '' # Start interactive shell exec zsh " exit $? fi log_info "Running Claude Code in container..." # Run the container with the command # The command will: initialize firewall, then run claude docker run --rm \ --name "$CONTAINER_NAME" \ --cap-add=NET_ADMIN \ --cap-add=NET_RAW \ -v "$(pwd):/workspace" \ -v "$VOLUME_HISTORY:/commandhistory" \ -v "$VOLUME_CONFIG:/home/node/.claude" \ -e NODE_OPTIONS="--max-old-space-size=4096" \ -e CLAUDE_CONFIG_DIR="/home/node/.claude" \ -e POWERLEVEL9K_DISABLE_GITSTATUS="true" \ -w /workspace \ --user node \ "$IMAGE_NAME" \ /bin/bash -c " # Initialize firewall echo 'Initializing firewall...' >&2 sudo /usr/local/bin/init-firewall.sh >&2 # Run claude with the prompt echo 'Running Claude Code...' >&2 claude -p \"$PROMPT\" --dangerously-skip-permissions " EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ]; then log_info "Command completed successfully" else log_error "Command failed with exit code $EXIT_CODE" exit $EXIT_CODE fi