Skip to content
This repository was archived by the owner on Feb 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"permissions": {
"allow": [
"Bash(mkdir:*)",
"Bash(npm run build:*)",
"Bash(grep:*)",
"Bash(ls:*)",
"Bash(docker logs:*)",
"Bash(docker kill:*)",
"Bash(docker rm:*)",
"Bash(npx tsc:*)",
"Bash(npm install:*)",
"Bash(node:*)",
"Bash(timeout:*)",
"Bash(true)",
"Bash(docker stop:*)",
"Bash(mv:*)",
"Bash(curl:*)",
"WebFetch(domain:localhost)",
"Bash(pkill:*)",
"Bash(docker exec:*)",
"Bash(npx ts-node:*)",
"Bash(docker pull:*)",
"Bash(rg:*)",
"Bash(npm start)",
"Bash(find:*)",
"Bash(npm run lint)",
"Bash(sed:*)",
"Bash(npx claude-sandbox purge:*)",
"Bash(docker cp:*)",
"Bash(npm run test:e2e:*)",
"Bash(gh pr list:*)",
"Bash(kill:*)",
"Bash(npm start:*)",
"Bash(npm run purge-containers:*)",
"Bash(claude-sandbox start:*)",
"Bash(npm run lint)"
],
"deny": []
},
"enableAllProjectMcpServers": false
}
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ npm install -g @textcortex/claude-code-sandbox
### Prerequisites

- Node.js >= 18.0.0
- Docker
- Docker or Podman
- Git
- Claude Code (`npm install -g @anthropic-ai/claude-code@latest`)

Expand Down Expand Up @@ -218,6 +218,7 @@ Create a `claude-sandbox.config.json` file (see `claude-sandbox.config.example.j
- `bashTimeout`: Timeout for bash commands in milliseconds
- `containerPrefix`: Custom prefix for container names
- `claudeConfigPath`: Path to Claude configuration file
- `dockerSocketPath`: Custom Docker/Podman socket path (auto-detected by default)

#### Mount Configuration

Expand All @@ -236,6 +237,27 @@ Example use cases:

## Features

### Podman Support

Claude Code Sandbox now supports Podman as an alternative to Docker. The tool automatically detects whether you're using Docker or Podman by checking for available socket paths:

- **Automatic detection**: The tool checks for Docker and Podman sockets in standard locations
- **Custom socket paths**: Use the `dockerSocketPath` configuration option to specify a custom socket
- **Environment variable**: Set `DOCKER_HOST` to override socket detection

Example configuration for Podman:

```json
{
"dockerSocketPath": "/run/user/1000/podman/podman.sock"
}
```

The tool will automatically detect and use Podman if:

- Docker socket is not available
- Podman socket is found at standard locations (`/run/podman/podman.sock` or `$XDG_RUNTIME_DIR/podman/podman.sock`)

### Web UI Terminal

Launch a browser-based terminal interface to interact with Claude Code:
Expand Down
3 changes: 2 additions & 1 deletion claude-sandbox.config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@
"maxThinkingTokens": 100000,
"bashTimeout": 600000,
"containerPrefix": "claude-code-sandbox",
"claudeConfigPath": "~/.claude.json"
"claudeConfigPath": "~/.claude.json",
"dockerSocketPath": null
}
34 changes: 33 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,37 @@ import Docker from "dockerode";
import { ClaudeSandbox } from "./index";
import { loadConfig } from "./config";
import { WebUIServer } from "./web-server";
import { getDockerConfig, isPodman } from "./docker-config";
import ora from "ora";

const docker = new Docker();
// Initialize Docker with config - will be updated after loading config if needed
let dockerConfig = getDockerConfig();
let docker = new Docker(dockerConfig);
const program = new Command();

// Helper function to reinitialize Docker with custom socket path
function reinitializeDocker(socketPath?: string) {
if (socketPath) {
dockerConfig = getDockerConfig(socketPath);
docker = new Docker(dockerConfig);

// Log if using Podman
if (isPodman(dockerConfig)) {
console.log(chalk.blue("Detected Podman socket"));
}
}
}

// Helper to ensure Docker is initialized with config
async function ensureDockerConfig() {
try {
const config = await loadConfig("./claude-sandbox.config.json");
reinitializeDocker(config.dockerSocketPath);
} catch (error) {
// Config loading failed, continue with default Docker config
}
}

// Helper function to get Claude Sandbox containers
async function getClaudeSandboxContainers() {
const containers = await docker.listContainers({ all: true });
Expand Down Expand Up @@ -123,6 +149,7 @@ program
.command("attach [container-id]")
.description("Attach to an existing Claude Sandbox container")
.action(async (containerId) => {
await ensureDockerConfig();
const spinner = ora("Looking for containers...").start();

try {
Expand Down Expand Up @@ -169,6 +196,7 @@ program
.description("List all Claude Sandbox containers")
.option("-a, --all", "Show all containers (including stopped)")
.action(async (options) => {
await ensureDockerConfig();
const spinner = ora("Fetching containers...").start();

try {
Expand Down Expand Up @@ -211,6 +239,7 @@ program
.description("Stop Claude Sandbox container(s)")
.option("-a, --all", "Stop all Claude Sandbox containers")
.action(async (containerId, options) => {
await ensureDockerConfig();
const spinner = ora("Stopping containers...").start();

try {
Expand Down Expand Up @@ -272,6 +301,7 @@ program
.option("-n, --tail <lines>", "Number of lines to show from the end", "50")
.action(async (containerId, options) => {
try {
await ensureDockerConfig();
let targetContainerId = containerId;

if (!targetContainerId) {
Expand Down Expand Up @@ -310,6 +340,7 @@ program
.description("Remove all stopped Claude Sandbox containers")
.option("-f, --force", "Remove all containers (including running)")
.action(async (options) => {
await ensureDockerConfig();
const spinner = ora("Cleaning up containers...").start();

try {
Expand Down Expand Up @@ -346,6 +377,7 @@ program
.option("-y, --yes", "Skip confirmation prompt")
.action(async (options) => {
try {
await ensureDockerConfig();
const containers = await getClaudeSandboxContainers();

if (containers.length === 0) {
Expand Down
61 changes: 61 additions & 0 deletions src/docker-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as fs from "fs";
import * as path from "path";

interface DockerConfig {
socketPath?: string;
}

/**
* Detects whether Docker or Podman is available and returns appropriate configuration
* @param customSocketPath - Optional custom socket path from configuration
*/
export function getDockerConfig(customSocketPath?: string): DockerConfig {
// Allow override via environment variable
if (process.env.DOCKER_HOST) {
return {}; // dockerode will use DOCKER_HOST automatically
}

// Use custom socket path if provided
if (customSocketPath) {
return { socketPath: customSocketPath };
}

// Common socket paths to check
const socketPaths = [
// Docker socket paths
"/var/run/docker.sock",

// Podman rootless socket paths
process.env.XDG_RUNTIME_DIR &&
path.join(process.env.XDG_RUNTIME_DIR, "podman", "podman.sock"),
`/run/user/${process.getuid?.() || 1000}/podman/podman.sock`,

// Podman root socket path
"/run/podman/podman.sock",
].filter(Boolean) as string[];

// Check each socket path
for (const socketPath of socketPaths) {
try {
if (fs.existsSync(socketPath)) {
const stats = fs.statSync(socketPath);
if (stats.isSocket()) {
return { socketPath };
}
}
} catch (error) {
// Socket might exist but not be accessible, continue checking
continue;
}
}

// No socket found, return empty config and let dockerode use its defaults
return {};
}

/**
* Checks if we're using Podman based on the socket path
*/
export function isPodman(config: DockerConfig): boolean {
return config.socketPath?.includes("podman") ?? false;
}
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ContainerManager } from "./container";
import { UIManager } from "./ui";
import { WebUIServer } from "./web-server";
import { SandboxConfig } from "./types";
import { getDockerConfig, isPodman } from "./docker-config";
import path from "path";

export class ClaudeSandbox {
Expand All @@ -21,7 +22,14 @@ export class ClaudeSandbox {

constructor(config: SandboxConfig) {
this.config = config;
this.docker = new Docker();
const dockerConfig = getDockerConfig(config.dockerSocketPath);
this.docker = new Docker(dockerConfig);

// Log if using Podman
if (isPodman(dockerConfig)) {
console.log(chalk.blue("Detected Podman socket"));
}

this.git = simpleGit();
this.credentialManager = new CredentialManager();
this.gitMonitor = new GitMonitor(this.git);
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface SandboxConfig {
targetBranch?: string;
remoteBranch?: string;
prNumber?: string;
dockerSocketPath?: string;
}

export interface Credentials {
Expand Down
Loading