opencode-cloud
[!WARNING] This tool is still a work in progress and is rapidly evolving. Expect frequent updates and breaking changes. Follow updates at https://github.com/pRizz/opencode-cloud (mirror: https://gitea.com/pRizz/opencode-cloud). Stability will be announced at some point. Use with caution.
A production-ready toolkit for deploying and managing opencode as a persistent cloud service, sandboxed inside a Docker container for isolation and security.
This project uses the opencode fork at https://github.com/pRizz/opencode, which adds additional authentication and security features.
Quick install (cargo)
Quick install (npm)
Or install globally:
Deploy to AWS
Quick deploy provisions a private EC2 instance behind a public ALB with HTTPS. A domain name is required for ACM certificate validation. A Route53 hosted zone ID is required for automated DNS validation.
Docs: docs/deploy/aws.md (includes teardown steps and S3 hosting setup for forks)
Credentials: docs/deploy/aws.md#retrieving-credentials
Deploy to DigitalOcean (Coming Soon)
DigitalOcean Marketplace one-click deployment is not implemented yet. Support is coming soon.
Warning: direct manual Droplet deployments are currently not recommended because persistence support is incomplete and data loss is likely.
For testing-only reference:
- Manual Droplet setup:
docs/deploy/digitalocean-droplet.md - Marketplace docs/status:
docs/deploy/digitalocean-marketplace.md
Features
- Sandboxed execution - opencode runs inside a Docker container, isolated from your host system
- Persistent environment - Your projects, settings, and shell history persist across restarts
- Cross-platform CLI (
opencode-cloud/occ) - Works on Linux and macOS - Service lifecycle commands - start, stop, restart, status, logs
- Platform service integration - systemd (Linux) / launchd (macOS) for auto-start on boot
- Remote host management - Manage opencode containers on remote servers via SSH
How it works
opencode-cloud runs opencode inside a Docker container, providing:
- Isolation - opencode and its AI-generated code run in a sandbox, separate from your host system
- Reproducibility - The container includes a full development environment (languages, tools, runtimes)
- Persistence - Docker volumes preserve your work across container restarts and updates
- Security - Network exposure is opt-in; by default, the service only binds to localhost
The CLI manages the container lifecycle, so you don't need to interact with Docker directly.
Docker Images
The sandbox container image is named opencode-cloud-sandbox (not opencode-cloud) to clearly distinguish it from the CLI tool. The preferred way to use and manage the image is via the opencode-cloud CLI (GitHub, mirror: https://gitea.com/pRizz/opencode-cloud). It handles image pulling, container setup, and upgrades for you.
Why use the CLI? It configures volumes, ports, and upgrades safely, so you don’t have to manage docker run flags or image updates yourself.
The image is published to both registries (Docker Hub is the primary distribution):
| Registry | Image |
|---|---|
| Docker Hub | prizz/opencode-cloud-sandbox |
| GitHub Container Registry | ghcr.io/prizz/opencode-cloud-sandbox |
Pull commands:
Docker Hub:
GitHub Container Registry:
For most users: Just use the CLI - it handles image pulling/building automatically:
Requirements
- Rust 1.85+ - Install via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Docker - For running the opencode container
- Supported platforms - Linux and macOS
Installation
Via cargo (recommended)
Via npm
Or install globally:
First run
# Install as a system service (recommended for background use)
# Start the service
If this is the first startup with no configured managed users, the container logs will print an Initial One-Time Password (IOTP).
Open the login page, use the first-time setup panel, and enroll a passkey for the default opencoder account.
The IOTP is invalidated after successful passkey enrollment.
Built-in image users (for example ubuntu/opencoder) do not count as configured users for IOTP bootstrap.
Quick IOTP extraction from logs:
| | |
You can also run the setup wizard:
The wizard now configures runtime settings (image source, bind/port, mounts), keeps authentication on IOTP + passkey onboarding, and attempts to auto-detect the IOTP from logs after start.
From source (install locally)
# GitHub (primary)
# Gitea (mirror)
From source (development run)
# GitHub (primary)
# Gitea (mirror)
# Bun is required for packages/opencode checks/builds
Usage
# Show version
# Start the service (builds Docker container on first run, ~10-15 min)
# Start on a custom port
# Start and open browser
# Check service status (includes broker health: Healthy/Degraded/Unhealthy)
# View logs
# Follow logs in real-time
# View opencode-broker logs (systemd/journald required)
# Troubleshoot broker health issues reported by `occ status`
# Note: Broker logs require systemd/journald. This is enabled by default on supported Linux
# hosts. Docker Desktop/macOS/Windows use Tini, so broker logs aren't available there.
# Existing containers may need to be recreated after upgrading.
# Stop the service
# Restart the service
# Check for updates and choose what to update
# Update the opencode-cloud CLI binary
# Update the opencode-cloud container image
# Update opencode inside the container
# Update opencode using a specific branch or commit
# Remove the container (keeps volumes)
# Remove container and volumes (data loss)
# Reset completed IOTP bootstrap and generate a fresh one-time password
# If bind_address is exposed (for example behind HTTPS reverse proxy), confirm intentionally
# Clean bind mount contents (data loss)
# Purge bind mounts (data loss, removes config entries)
# Mount a local project into the workspace
# Apply mount changes (you may be prompted to recreate the container)
# Factory reset host (container, volumes, mounts, config/data)
### Workspace Mounts
Verify the mount:
- Run
occ statusand checkMounts->Bind mountsincludes your host path mapped to/home/opencoder/workspace. - In the web UI, open the project picker and confirm your project files appear under
~/workspace.
Container Mode
When occ runs inside the opencode container, it will auto-detect this and switch to container runtime.
Override if needed:
OPENCODE_RUNTIME=host
Supported commands in container runtime:
occ statusocc logsocc userocc update opencode
Notes:
- Host/Docker lifecycle commands are disabled in container runtime.
occ logsandocc update opencoderequire systemd inside the container. If systemd is not available, run those commands from the host instead.
Webapp-triggered update (command file)
When running in foreground mode (for example via occ install, which uses occ start --no-daemon),
the host listens for a command file on a bind mount. The webapp can write a simple JSON payload
to request an update.
Default paths (with default bind mounts enabled):
- Host:
~/.local/state/opencode/opencode-cloud/commands/update-command.json - Container:
/home/opencoder/.local/state/opencode/opencode-cloud/commands/update-command.json
Example payload:
The host writes the result to:
~/.local/state/opencode/opencode-cloud/commands/update-command.result.json
Install as a system service (starts on login/boot)
occ install
Uninstall the system service
occ uninstall
View configuration
occ config show
## Authentication
opencode-cloud uses **PAM (Pluggable Authentication Modules)** for authentication.
First boot path:
- If no managed users are configured, startup logs print an Initial One-Time Password (IOTP).
- Extract only the IOTP quickly: `occ logs | grep -F "INITIAL ONE-TIME PASSWORD (IOTP): " | tail -n1 | sed 's/.*INITIAL ONE-TIME PASSWORD (IOTP): //'`
- `occ setup` attempts to auto-detect and print the IOTP after starting/restarting the service.
- Enter that IOTP in the web login page first-time setup panel.
- Enroll a passkey for the default `opencoder` account.
- The IOTP is deleted after successful passkey enrollment.
- To restart first-time onboarding after completion, run `occ reset iotp`.
- Built-in image users (for example `ubuntu`/`opencoder`) do not disable IOTP bootstrap.
Admin path:
- You can always create/manage users directly via `occ user add`, `occ user passwd`, and related user commands.
Login UX:
- Passkey sign-in is the primary option on the login page.
- Username/password sign-in remains available as fallback.
- 2FA setup/management is available from the upper-right session menu after login.
### Creating Users
Create a user with a password:
```bash
occ user add <username>
Generate a random password:
Managing Users
- List users:
occ user list(managed users only) - Change password:
occ user passwd <username> - Remove user:
occ user remove <username> - Enable/disable account:
occ user enable <username>/occ user disable <username>
User Persistence
User accounts (including password hashes and lock status) persist across container updates and rebuilds.
The CLI stores user records in a managed Docker volume mounted at /var/lib/opencode-users inside the container.
This record store is the source of truth for configured users and bootstrap decisions.
No plaintext passwords are stored on the host.
Rebuilding the Docker Image
When developing locally or after updating opencode-cloud, you may need to rebuild the Docker image to pick up changes in the embedded Dockerfile:
# Rebuild using Docker cache (fast - only rebuilds changed layers)
# Rebuild from scratch without cache (slow - for troubleshooting)
--cached-rebuild-sandbox-image (recommended for most cases):
- Uses Docker layer cache for fast rebuilds
- Only rebuilds layers that changed (e.g., if only the CMD changed, it's nearly instant)
- Stops and removes any existing container before rebuilding
--full-rebuild-sandbox-image (for troubleshooting):
- Ignores Docker cache and rebuilds everything from scratch
- Takes 10-15 minutes but guarantees a completely fresh image
- Use when cached rebuild doesn't fix issues
When to rebuild:
- After pulling updates to opencode-cloud → use
--cached-rebuild-sandbox-image - After pulling new commits in
packages/opencode(submodule) → runjust run start --cached-rebuild-sandbox-imageonce so the running container picks up the new opencode commit - When modifying the Dockerfile during development → use
--cached-rebuild-sandbox-image - When the container fails to start due to image issues → try
--cached-rebuild-sandbox-imagefirst, then--full-rebuild-sandbox-image - When you want a completely fresh environment → use
--full-rebuild-sandbox-image
Local submodule dev rebuild (no push required):
# Fast rebuild using local packages/opencode checkout (including uncommitted edits)
# Full no-cache rebuild from local packages/opencode checkout
- This mode is for local development/debugging only and bypasses the default remote clone path.
just run statuswill show commit metadata derived from your local checkout (dirty trees are marked with-dirty).- Local context packaging intentionally skips heavyweight/dev metadata folders (for example
.planning,.git,node_modules,target, anddist). - Keep CI/release workflows on the default pinned remote mode.
Dockerfile Optimization Checklist
For new Docker build steps, follow this checklist:
- Prefer BuildKit cache mounts (
RUN --mount=type=cache) for package caches (apt,bun,cargo,pip, andpnpm/npm). - Create and remove temporary workdirs in the same
RUNlayer (for example/tmp/opencode-repo). - Do not defer cleanup to later layers; deleted files still exist in lower layers.
- Keep builder-stage artifacts out of runtime layers by copying only final outputs.
- When adding Docker build assets, update build-context inclusion logic in
packages/core/src/docker/image.rs. - Keep local submodule exclude lists aligned with Dockerfile hygiene rules (
.planning,.git,node_modules,target,dist, and similar dev metadata).
This policy is intentional for both image-size hygiene and fast local rebuilds.
Worktree-isolated sandbox profiles (opt-in):
# Derive a stable instance name from the current git worktree root
# Use an explicit instance name
- Default behavior (no
--sandbox-instance) remains the shared legacy sandbox. - Isolated instances use separate container names, image tags, Docker volumes, and image-state files.
- You can also set
OPENCODE_SANDBOX_INSTANCE=<name|auto>instead of passing the CLI flag every time.
Configuration
Configuration is stored at:
- Linux/macOS:
~/.config/opencode-cloud/config.json
Data (PID files, etc.) is stored at:
- Linux/macOS:
~/.local/share/opencode-cloud/
Development
# Bun is required for packages/opencode checks/builds
# One-time setup (hooks + deps + submodule bootstrap)
# Build everything
# Compile and run occ (arguments automatically get passed to the binary)
# Run tests
# Format and lint
Visual E2E (Playwright)
From the repository root, run UI tests in a visible browser with:
PWDEBUG=1
Headless run (default Playwright mode):
Use test:e2e:local for local harness/sandbox provisioning. Plain test:e2e expects a backend that is already running at the configured Playwright host/port.
Note: The git hooks automatically sync
README.mdto npm package directories on commit.
Architecture
This is a monorepo with:
packages/core- Rust core librarypackages/cli-rust- Rust CLI binary (recommended)packages/cli-node- Node.js CLI (fully supported and in parity with the Rust CLI)
Cargo.toml Sync Requirement
The packages/core/Cargo.toml file must use explicit values rather than workspace = true references.
When updating package metadata (version, edition, rust-version, etc.), keep both files in sync:
Cargo.toml(workspace root)packages/core/Cargo.toml
Use scripts/set-all-versions.sh <version> to update versions across all files automatically.
License
MIT
