Your agent: containerized with its own browser for manual testing. Your terminal: pair live or share recordings with teammates. Your sessions: run multiple in parallel, each on its own git worktree.
Works with Claude, Aider, Goose, Gemini, Codex, OpenCode. Not listed? Let us know!
swe-swe.mov.mp4
-
Download the swe-swe binary for your platform:
# Build or download swe-swe binary # Available binaries are in ./dist/swe-swe after building
-
Initialize a project
swe-swe init --project-directory /path/to/your/project
-
Start the environment
swe-swe up --project-directory /path/to/your/project
-
Access the services (use
https://if initialized with--ssl=selfsign)- swe-swe terminal: http://0.0.0.0:1977
- VSCode: http://0.0.0.0:1977/vscode
- Chrome VNC: http://0.0.0.0:1977/chrome (browser automation viewer)
- Traefik dashboard: http://0.0.0.0:1977/dashboard/
-
View all initialized projects
swe-swe list
-
Stop the environment
swe-swe down --project-directory /path/to/your/project
- Docker & Docker Compose installed
- Terminal access (works on macOS, Linux, Windows with WSL)
swe-swe has three native commands (init, list, and proxy). All other commands are passed directly to docker compose with the project's configuration.
Initializes a new swe-swe project at the specified path. Creates metadata directory at $HOME/.swe-swe/projects/{sanitized-path}/ with:
- Dockerfile: Container image definition with Node.js, Go, and optional tools
- docker-compose.yml: Multi-container orchestration
- traefik-dynamic.yml: Routing configuration
- swe-swe-server/: Source code for the AI terminal server (built at docker-compose time)
- home/: Persistent storage for VSCode settings and shell history
- certs/: Enterprise certificates (if detected)
- .path: Records original project path (used for project discovery)
Options:
--project-directory PATH: Project directory (defaults to current directory)--previous-init-flags=reuse: Reapply saved configuration from previous init (cannot be combined with other flags)--previous-init-flags=ignore: Ignore saved configuration, use provided flags for fresh init--agents AGENTS: Comma-separated list of agents to include (default: all)--exclude-agents AGENTS: Comma-separated list of agents to exclude--apt-get-install PACKAGES: Additional apt packages to install--npm-install PACKAGES: Additional npm packages to install globally--with-docker: Mount Docker socket to allow container to run Docker commands on host--with-slash-commands REPOS: Git repos to clone as slash commands for Claude, Codex, and OpenCode (space-separated, format:[alias@]<git-url>)--ssl MODE: SSL/TLS mode -no(default, HTTP) orselfsign(HTTPS with auto-generated self-signed certificate)--copy-home-paths PATHS: Comma-separated paths relative to$HOMEto copy into the container's home directory (e.g.,.gitconfig,.ssh/config)--status-bar-color COLOR: Status bar background color (default:#007acc). Use--status-bar-color=listto see available preset colors.--terminal-font-size SIZE: Terminal font size in pixels (default:14)--terminal-font-family FONT: Terminal font family (default:Menlo, Monaco, "Courier New", monospace)--status-bar-font-size SIZE: Status bar font size in pixels (default:12)--status-bar-font-family FONT: Status bar font family (default: system sans-serif)
Available Agents: claude, gemini, codex, aider, goose, opencode
Examples:
# Initialize current directory with all agents (default)
swe-swe init
# Initialize current directory with Claude only (minimal, fastest build)
swe-swe init --agents=claude
# Initialize current directory with Claude and Gemini
swe-swe init --agents=claude,gemini
# Initialize current directory without Python-based agents (no aider)
swe-swe init --exclude-agents=aider
# Initialize current directory with additional system packages
swe-swe init --apt-get-install="vim htop tmux"
# Initialize current directory with Docker access
swe-swe init --with-docker
# Initialize current directory with custom slash commands for Claude/Codex/OpenCode
swe-swe init --with-slash-commands=ck@https://github.com/choonkeat/slash-commands.git
# Initialize with HTTPS (self-signed certificate)
swe-swe init --ssl=selfsign
# Initialize with git and SSH config copied from host
swe-swe init --copy-home-paths=.gitconfig,.ssh/config
# Initialize a specific directory
swe-swe init --project-directory ~/my-project
# Reinitialize with same configuration (after updates)
swe-swe init --previous-init-flags=reuse
# Reinitialize with new configuration (overwrite previous)
swe-swe init --previous-init-flags=ignore --agents=claudeSecurity Note on --with-docker: Mounting the Docker socket grants the container effective root access to the host. The container can mount host filesystems, run privileged containers, and access other containers. Only use this flag when you trust the code running inside the container (e.g., for your own projects, not untrusted third-party code).
Dependency Optimization: The Dockerfile is automatically optimized based on selected agents:
- Python/pip is only installed if
aideris included - Node.js/npm is only installed if
claude,gemini,codex, oropencodeis included - This can significantly reduce image size and build time
Services are accessible via path-based routing on localhost (or 0.0.0.0) at the configured port.
Note on Port: The port defaults to 1977 and can be customized via the SWE_PORT environment variable (e.g., SWE_PORT=8080 swe-swe up). Services are routed based on request paths (e.g., /vscode, /chrome) rather than subdomains, making it compatible with ngrok, cloudflared, and other tunnel services.
Enterprise Certificate Support: If you're behind a corporate firewall or VPN, the init command automatically detects and copies certificates from:
NODE_EXTRA_CA_CERTSSSL_CERT_FILENODE_EXTRA_CA_CERTS_BUNDLE
These are copied to .swe-swe/certs/ and mounted into all containers.
Lists all initialized swe-swe projects and automatically prunes stale ones.
What it does:
- Scans
$HOME/.swe-swe/projects/directory - For each project, reads
.pathfile to get original path - Checks if original path still exists
- Auto-removes metadata directories for deleted projects (pruning)
- Displays remaining active projects with count
- Shows summary of pruned projects
When to use:
- Discover what projects you have initialized
- Clean up metadata for deleted projects
- Verify project paths before cleanup
Example:
swe-swe list
# Output:
# Initialized projects (2):
# /Users/alice/projects/myapp
# /Users/alice/projects/anotherapp
#
# Removed 1 stale project(s)Bridges the container-host boundary by letting containers execute specific host commands with real-time output streaming. This is the secure alternative to --with-docker when you only need access to specific commands rather than full Docker socket access.
When to use proxy vs --with-docker:
| Scenario | Solution |
|---|---|
AI agent needs to run make with host toolchain |
swe-swe proxy make |
Need docker build without exposing full Docker socket |
swe-swe proxy docker |
| Access host's SSH agent for git operations | swe-swe proxy git |
| Full Docker/container management needed | --with-docker flag |
How it works:
- Host runs
swe-swe proxy <command>which watches.swe-swe/proxy/for requests - A container script is generated at
.swe-swe/proxy/<command> - Container runs
.swe-swe/proxy/<command> [args...]to submit a request - Host executes the command and streams stdout/stderr back to container in real-time
- Container exits with the command's exit code
Key features:
- Real-time streaming: Output appears immediately, not after command completes
- Exit code propagation: Container script exits with the host command's exit code
- Stdin forwarding: Piped/redirected input is forwarded to the host command (e.g.,
echo "data" | .swe-swe/proxy/cat) - Concurrent requests: Multiple container processes can use the same proxy simultaneously
- Efficient file watching: Uses inotify when available, falls back to polling
Examples:
# Terminal 1: Start proxy for 'make' command
swe-swe proxy make
# [proxy] Listening for 'make' commands... (Ctrl+C to stop)
# Terminal 2 (inside container): Run make with arguments
.swe-swe/proxy/make build TARGET=hello
# Output streams in real-time, exits with make's exit code
# Multiple proxies (run each in separate terminals)
swe-swe proxy make
swe-swe proxy docker
swe-swe proxy npmReal-world use cases:
# Cross-compile with host toolchain
swe-swe proxy make
# In container: .swe-swe/proxy/make build-linux-arm64
# Run Docker commands without --with-docker
swe-swe proxy docker
# In container: .swe-swe/proxy/docker build -t myapp .
# Use host's authenticated npm registry
swe-swe proxy npm
# In container: .swe-swe/proxy/npm publishEnvironment Variables:
PROXY_TIMEOUT: Timeout in seconds for container script (default: 300)PROXY_DIR: Override proxy directory (default:.swe-swe/proxy)
File protocol:
.swe-swe/proxy/
├── <command> # Generated container script (executable)
├── <command>.pid # Host PID file (prevents duplicate proxies)
├── <uuid>.req # Request file (NUL-delimited args)
├── <uuid>.stdin # Request stdin (if piped/redirected input)
├── <uuid>.stdout # Response stdout (streamed)
├── <uuid>.stderr # Response stderr (streamed)
└── <uuid>.exit # Exit code (signals completion)
Cleanup responsibilities:
| File | Cleaned up by | When |
|---|---|---|
<uuid>.req |
Host | After reading (claims the request) |
<uuid>.stdout/stderr/exit |
Container | On script exit (via trap) |
<command>, <command>.pid |
Host | On proxy shutdown (Ctrl+C) |
| Orphan response files | Host | On startup (files older than 5 min) |
All commands other than init, list, and proxy are passed directly to docker compose using the project's generated docker-compose.yml. This means you can use any docker compose command:
swe-swe up # docker compose up
swe-swe down # docker compose down
swe-swe build # docker compose build
swe-swe ps # docker compose ps
swe-swe logs -f swe-swe # docker compose logs -f swe-swe
swe-swe exec swe-swe bash # docker compose exec swe-swe bash
swe-swe restart chrome # docker compose restart chromeUse --project-directory to specify which project (defaults to current directory):
swe-swe up --project-directory ~/my-project
swe-swe logs -f --project-directory ~/my-projectStarts the swe-swe environment using docker compose up. The environment includes:
- swe-swe: WebSocket-based AI terminal with session management
- chrome: Headless Chromium with VNC for browser automation (used by MCP Playwright)
- code-server: VS Code IDE running in a container
- vscode-proxy: Nginx proxy for VSCode path routing
- traefik: HTTP reverse proxy with path-based routing rules
- auth: ForwardAuth service for unified password protection
The workspace is mounted at /workspace inside containers, allowing bidirectional file access.
Example:
swe-swe up --project-directory ~/my-project
# Press Ctrl+C to stopEnvironment Variables (passed through automatically):
ANTHROPIC_API_KEY: Claude API key (passed by default)SWE_SWE_PASSWORD: Authentication password for all services (defaults tochangeme)SWE_PORT: External port (defaults to 1977)NODE_EXTRA_CA_CERTS: Enterprise CA certificate path (auto-copied during init)SSL_CERT_FILE: SSL certificate file path (auto-copied during init)BROWSER_WS_ENDPOINT: WebSocket endpoint for browser automation (auto-configured tows://chrome:9223)
Additional API keys (uncomment in docker-compose.yml if needed):
OPENAI_API_KEY: OpenAI API key (for Codex)GEMINI_API_KEY: Google Gemini API key
Stops and removes the running Docker containers for the project.
Example:
swe-swe down --project-directory ~/my-projectRebuilds the Docker image from scratch (clears cache). Useful when:
- Updating the base image
- Installing new dependencies in Dockerfile
- Testing fresh builds
Example:
swe-swe build --project-directory ~/my-projectDisplays the help message with all available commands.
Project metadata is stored in $HOME/.swe-swe/projects/{sanitized-path}/:
$HOME/.swe-swe/projects/{sanitized-path}/
├── Dockerfile # Container image definition (multi-stage build)
├── docker-compose.yml # Service orchestration
├── traefik-dynamic.yml # HTTP routing rules
├── .dockerignore # Docker build exclusions
├── .path # Original project path (for discovery)
├── init.json # Saved init flags (for --previous-init-flags=reuse)
├── entrypoint.sh # Container entrypoint script
├── nginx-vscode.conf # Nginx config for VSCode proxy
├── swe-swe-server/ # Server source code (built at docker-compose time)
│ ├── go.mod, go.sum
│ ├── main.go
│ └── static/
├── auth/ # ForwardAuth service source code
├── chrome/ # Chrome/noVNC service (Dockerfile, configs)
├── home/ # Persistent VSCode/shell home (volume)
├── certs/ # Enterprise certificates (if detected)
└── tls/ # Self-signed TLS certificates (if --ssl=selfsign)
Why metadata is stored outside the project:
- Prevents container access to infrastructure configuration
- Allows metadata cleanup via
swe-swe listcommand - Centralizes all metadata in one location
- Your project directory remains clean (no
.swe-swe/)
- Port: 9898 (inside container)
- Purpose: WebSocket-based AI coding assistant terminal
- Features:
- Real-time terminal with PTY support
- Session management (sessions persist until process exits)
- Multiple AI assistant detection (claude, gemini, codex, goose, aider, opencode)
- Automatic process restart on error exit (non-zero exit code); clean exit (code 0) ends session
- File upload via drag-and-drop (saved to
.swe-swe/uploads/) - In-session chat for collaboration
- YOLO mode toggle for supported agents (auto-approve actions)
- Split-pane UI: Side panel with Preview, Browser, and Shell tabs for app preview and debugging
- Session recordings: Automatic terminal recording with playback (streaming mode for large recordings)
- Debug channel: Agents can receive console logs, errors, and network requests from the App Preview via
swe-swe-server --debug-listen
- Ports: 9223 (CDP), 6080 (VNC/noVNC)
- Purpose: Headless Chromium browser for AI-driven browser automation
- Features:
- Chrome DevTools Protocol (CDP) access via nginx proxy
- VNC server for visual observation at
/chromepath (e.g., http://0.0.0.0:1977/chrome) - Used by MCP Playwright for browser automation tasks
- Enterprise SSL certificate support via NSS database
- Documentation: See
docs/browser-automation.md
- Port: 8080 (inside container)
- Purpose: Full VS Code IDE in the browser
- Features:
- Syntax highlighting, extensions support
- Direct file editing in
/workspace - Terminal integration
- Port: 7000 HTTP or 7443 HTTPS (external port 1977)
- Purpose: Reverse proxy and routing with path-based request matching
- Routing Rules:
/swe-swe-auth/*path: Auth service for ForwardAuth (priority 200)/vscodepath: Routes to code-server with path prefix stripped (priority 100)/chromepath: Routes to chrome service with path prefix stripped (priority 100)/dashboardpath: Traefik dashboard (priority 100)/path: Routes to swe-swe-server (priority 10, catch-all)
- Authentication: All routes (except auth) protected by ForwardAuth middleware
- Port: 4180 (internal)
- Purpose: ForwardAuth service for unified authentication
- Features:
- Cookie-based session management
- Redirect to original URL after login
- Mobile-responsive login page
Each project gets its own isolated Docker network ({PROJECT_NAME}_swe-network). This provides:
- Service discovery by container name within each project
- True network isolation between projects (no DNS conflicts)
- Multiple stacks can run simultaneously without interference
The Dockerfile is located in $HOME/.swe-swe/projects/{sanitized-path}/Dockerfile. Edit it to:
- Add Python:
apt-get install -y python3 python3-pip - Add Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - Add other tools via
apt-get
UID:GID Placeholders: The Dockerfile supports {{UID}} and {{GID}} placeholders that are replaced with the host user's UID/GID at init time. This ensures files created in the container have matching permissions on the host, eliminating permission conflicts when editing files from both sides.
Claude (passed through by default):
export ANTHROPIC_API_KEY=sk-ant-...
swe-swe up --project-directory ~/my-projectGemini/Codex (requires editing docker-compose.yml to uncomment the env vars):
# First, edit $HOME/.swe-swe/projects/{sanitized-path}/docker-compose.yml
# Uncomment the GEMINI_API_KEY and/or OPENAI_API_KEY lines in the swe-swe service
export GEMINI_API_KEY=...
export OPENAI_API_KEY=sk-...
swe-swe up --project-directory ~/my-projectAlternatively, create a .env file in $HOME/.swe-swe/projects/{sanitized-path}/:
ANTHROPIC_API_KEY=sk-ant-...
GEMINI_API_KEY=...
OPENAI_API_KEY=sk-...
Edit $HOME/.swe-swe/projects/{sanitized-path}/docker-compose.yml to adjust resource constraints:
code-server:
deploy:
resources:
limits:
cpus: '2'
memory: 2GThe default VSCode password is changeme. To use a custom password, set the SWE_SWE_PASSWORD environment variable:
# Default password (changeme)
swe-swe up --project-directory ~/my-project
# Custom password
SWE_SWE_PASSWORD='my-secure-password' swe-swe up --project-directory ~/my-projectFor detailed build architecture and troubleshooting, see docs/cli-commands-and-binary-management.md.
# Build CLI binaries for all platforms
make build
# Run tests
make testThe swe-swe-server is built from source at docker-compose build time using a multi-stage Dockerfile. This means:
- No pre-compiled server binaries are embedded in the CLI
- The server is always compiled fresh when the Docker image is built
- Changes to server source code in
cmd/swe-swe/templates/host/swe-swe-server/are reflected afterswe-swe build
.
├── cmd/
│ └── swe-swe/ # CLI tool
│ ├── *.go # main, init, docker, templates, certs, paths, list, proxy, color, ansi
│ └── templates/ # Embedded Docker/server files
│ └── host/
│ ├── Dockerfile, docker-compose.yml, etc.
│ └── swe-swe-server/ # WebSocket server source
├── docs/ # Technical documentation
├── Makefile # Build targets
├── go.mod, go.sum # Go dependencies
└── README.md # This file
- gorilla/websocket: WebSocket support for real-time terminal
- creack/pty: PTY (pseudo-terminal) for shell sessions
- vt10x: VT100 terminal emulation
- uuid: Session ID generation
Error: exec format error
Solution: Rebuild the container image which compiles the server from source:
swe-swe build --project-directory ~/my-projectError: port 1977 already allocated
Solution: Stop other projects or use a custom port via environment variable:
# Use a different port
SWE_PORT=8080 swe-swe up --project-directory ~/my-projectAlternatively, modify $HOME/.swe-swe/projects/{sanitized-path}/docker-compose.yml to change the port mapping.
Error: no AI assistants available
Solution: The server didn't detect an installed assistant:
- Set API keys:
ANTHROPIC_API_KEY(Claude),GEMINI_API_KEY(Gemini),OPENAI_API_KEY(Codex) - Or install CLI tools:
claude,gemini,codex,goose,aider,opencode
Error: Service not accessible at configured port (default 1977)
Solution:
- Verify Docker is running:
docker ps - Check containers are healthy:
swe-swe ps --project-directory ~/my-project - Check Traefik logs:
swe-swe logs traefik --project-directory ~/my-project - Verify you're using correct paths:
http://0.0.0.0:1977/,http://0.0.0.0:1977/vscode,http://0.0.0.0:1977/chrome
If VSCode settings/extensions don't persist:
- Verify
$HOME/.swe-swe/projects/{sanitized-path}/home/exists and has correct permissions - Check that the metadata directory wasn't accidentally deleted
- Reinitialize the project:
swe-swe init --project-directory /path/to/project
Each project gets its own isolated environment. No conflicts:
# Terminal 1
swe-swe init --project-directory ~/project1
swe-swe up --project-directory ~/project1
# Terminal 2
swe-swe init --project-directory ~/project2
swe-swe up --project-directory ~/project2
# Use different ports if accessing locallyUse a custom shell by modifying the Dockerfile CMD:
ENV SHELL=/bin/zsh
CMD ["/usr/local/bin/swe-swe-server", "-shell", "zsh", "-working-directory", "/workspace", "-addr", "0.0.0.0:9898"]Sessions persist until the shell process exits. When a shell exits with code 0 (success), the session ends cleanly. When a shell crashes or exits with a non-zero code, it automatically restarts after 500ms.
To customize the shell command, modify the Dockerfile CMD:
CMD ["/usr/local/bin/swe-swe-server", "-shell", "bash", "-working-directory", "/workspace", "-addr", "0.0.0.0:9898"]YOLO mode allows agents to auto-approve actions without user confirmation. Toggle it via:
- Status bar: Click "Connected" text (changes to "YOLO" when active)
- Settings panel: Use the YOLO toggle switch
Supported agents and their YOLO commands:
| Agent | YOLO Command |
|---|---|
| Claude | --dangerously-skip-permissions |
| Gemini | --approval-mode=yolo |
| Codex | --yolo |
| Goose | GOOSE_MODE=auto |
| Aider | --yes-always |
| OpenCode | Not supported (toggle hidden) |
When toggled, the agent restarts with the appropriate YOLO flag. The status bar shows "YOLO" with a border indicator when active.
The App Preview (port 3000 by default) includes a debug channel that forwards browser events to agents. This lets agents debug what users see without needing visual access.
Listening for debug messages:
# Inside container - receive console logs, errors, fetch/XHR requests
swe-swe-server --debug-listenOutput is JSON lines:
{"t":"console","m":"log","args":["Hello!"],"ts":...}
{"t":"error","msg":"Uncaught TypeError","stack":"...","ts":...}
{"t":"fetch","url":"/api/users","method":"GET","status":200,"ms":45,"ts":...}Querying DOM elements:
# Query element by CSS selector
swe-swe-server --debug-query ".error-message"Response:
{"t":"queryResult","found":true,"text":"Invalid email","visible":true}Prerequisites:
- User must have the Preview tab open in the split-pane UI
- App must be running on port 3000 (or
SWE_PREVIEW_TARGET_PORT)
For detailed usage, see the /debug-with-app-preview slash command.
All services are protected by ForwardAuth by default. The authentication password is set via the SWE_SWE_PASSWORD environment variable (defaults to changeme).
# Use default password
swe-swe up --project-directory ~/my-project
# Use custom password
SWE_SWE_PASSWORD='my-secure-password' swe-swe up --project-directory ~/my-projectThe auth service provides:
- Cookie-based session management (expires when browser closes)
- Redirect to original URL after login
- Mobile-responsive login page
The swe-swe-server provides a WebSocket endpoint at /ws/{sessionId} for terminal communication.
Message Format:
{
"type": "input" | "resize",
"data": "shell input" | {"rows": 24, "cols": 80},
"sessionId": "uuid"
}See docs/websocket-protocol.md for detailed specification.
Follow Go conventions:
make fmt # Format code
make test # Run tests- Modify the appropriate component (CLI in
cmd/swe-swe, server incmd/swe-swe/templates/host/swe-swe-server) - Test locally:
make build && swe-swe init && swe-swe up - Commit with conventional commits:
fix:,feat:,docs:, etc.
See LICENSE file for details.