Build Command
The cc-deck build command provisions developer environments from a declarative manifest.
It supports three targets: container images built with Podman, OpenShell sandbox images with security policies, and remote machines provisioned over SSH with Ansible.
A single manifest captures your local tools, shell configuration, Claude Code plugins, and MCP servers, then generates the correct artifacts for whichever target you choose.
Prerequisites
Before using the build command, make sure the following tools are installed:
- cc-deck CLI
-
Install via Homebrew:
brew install cc-deck/tap/cc-deck. - Claude Code
-
Required for the
/cc-deck.captureand/cc-deck.buildslash commands that drive the capture and build workflows. - Ansible (SSH targets only)
-
Required for SSH provisioning. Install via Homebrew on macOS:
brew install ansible. The build command checks for Ansible availability and reports a clear error if it is missing. - Podman (container targets only)
-
Required for building and running container images. See Installation for installation details.
Container Workflow
The container workflow generates a Containerfile from your manifest, builds an OCI image, and optionally pushes it to a registry.
1. Initialize the Build Directory
cd your-project
cc-deck build init --target container
This creates a .cc-deck/setup/ directory containing:
-
cc-deck-build.yaml(manifest template with the container target section enabled) -
build-context/directory for binaries and configuration files -
.gitignorefor generated artifacts
It also installs two Claude Code slash commands into .claude/commands/:
-
cc-deck.capture.mdfor environment discovery -
cc-deck.build.mdfor artifact generation
2. Capture Your Environment
Run the capture command inside Claude Code:
/cc-deck.capture
The command scans your project for build files, CI configurations, version files, and shell settings. It discovers installed Claude Code plugins and MCP server configurations. For each section, it presents its findings and lets you accept, edit, or skip.
Capture is target-agnostic. It populates the shared sections of the manifest (tools, settings, plugins, MCP servers) without touching the target-specific sections.
3. Build the Image
Run the build command inside Claude Code:
/cc-deck.build --target container
Claude generates a Containerfile from the manifest, builds the image using Podman, and reports the result (image name and size). If the build fails, Claude reads the error output, fixes the Containerfile, and retries automatically (up to 3 attempts).
To push the image to the registry configured in targets.container.registry:
/cc-deck.build --target container --push
If a Containerfile already exists from a previous build, Claude shows a diff between the existing file and the newly generated version and asks you to choose which to use.
SSH Workflow
The SSH workflow generates Ansible playbooks from your manifest and runs them against a remote machine. After the initial provisioning converges, the playbooks can be re-run standalone without Claude Code involvement.
1. Initialize the Build Directory
cd your-project
cc-deck build init --target ssh
This creates the same .cc-deck/setup/ directory as the container workflow, plus Ansible role skeletons:
roles/
├── base/ # User creation, SSH keys, core packages
├── tools/ # Development tools from manifest
├── zellij/ # Zellij binary download
├── claude/ # Claude Code via official installer
├── cc_deck/ # cc-deck CLI and plugin installation
├── shell_config/ # Shell RC, credential sourcing, starship
└── mcp/ # MCP server configuration
Each role contains tasks/ and defaults/ directories.
2. Configure the SSH Target
Edit the targets.ssh section in .cc-deck/setup/cc-deck-build.yaml:
targets:
ssh:
host: dev@your-server
port: 22
identity_file: ~/.ssh/id_ed25519
create_user: true
user: dev
workspace: ~/workspace
When create_user is set to true, the Ansible base role creates the specified user with sudo access.
It reads the public key matching identity_file (appending .pub) and installs it as the new user’s authorized key.
3. Capture Your Environment
Run the capture command inside Claude Code, exactly as in the container workflow:
/cc-deck.capture
The capture command works the same way for both targets. It populates the shared manifest sections and leaves the SSH target configuration untouched.
4. Build (Provision the Remote)
Run the build command inside Claude Code:
/cc-deck.build --target ssh
Claude generates Ansible playbooks from the manifest, including an inventory file, group variables, a site.yml entry point, and role task files.
It then runs ansible-playbook against the remote host.
If a task fails, Claude reads the Ansible error output, fixes the relevant role, and retries (up to 3 attempts). Already-succeeded tasks are skipped on retry because Ansible playbooks are idempotent.
After convergence, you can re-run the playbooks from the command line without Claude:
cd .cc-deck/setup
ansible-playbook -i inventory.ini site.yml
The provisioning installs Zellij, Claude Code (via the official installer), the cc-deck CLI (from GitHub Releases), the cc-deck config plugin (via cc-deck config plugin install), and your shell configuration with credential sourcing enabled.
5. Verify the Remote
cc-deck build verify --target ssh
The verify command connects to the remote host via SSH and runs the same tool checks as the container verification. Each tool gets a pass or fail result.
6. Register an Environment
After provisioning, register the remote as a cc-deck environment:
cc-deck ws new my-remote --type ssh --host dev@your-server
The create command runs a lightweight probe (which zellij && which cc-deck && which claude) to confirm the host is provisioned.
If any tool is missing, the command fails with a message directing you to run cc-deck build first.
See SSH Environments for details on attaching to SSH environments.
OpenShell Workflow
The OpenShell workflow builds a sandbox container image with a security policy that controls network access per binary.
Unlike the container workflow, OpenShell images include a policy file (/etc/openshell/policy.yaml) that maps each tool to the endpoints it is allowed to reach.
The build uses a two-pass process to discover where tools are actually installed inside the image. This removes the need for a static lookup table and produces correct binary paths regardless of install method or base image.
1. Initialize the Build Directory
cd your-project
cc-deck build init --target openshell
This creates a .cc-deck/setup/ directory containing:
-
build.yaml(manifest template with the OpenShell target section enabled) -
openshell/snippets/(pre-rendered Containerfile fragments) -
openshell/components/(cached catalog policy components, fetched from the remote repository) -
openshell/policy.yaml(assembled security policy)
It also installs the Claude Code slash commands into .claude/commands/.
2. Capture Your Environment
Run the capture command inside Claude Code:
/cc-deck.capture
Capture works the same way across all targets. It populates the shared manifest sections (tools, settings, plugins, MCP servers) and leaves the target-specific sections untouched.
3. Build the Image
Run the build command inside Claude Code:
/cc-deck.build --target openshell
Claude generates a Containerfile from the manifest snippets, builds the image using Podman, and reports the result. If the build fails, Claude reads the error output, fixes the Containerfile, and retries automatically.
Two-Pass Binary Probing
When the manifest includes tool-matched components (such as Python, Rust, Go, or Node), the build runs in two passes:
- Pass 1
-
The image is built with all tools installed but without binary restrictions in the policy. Every network policy entry has its endpoints defined but its
binariesfield left empty. This allows the image to build successfully because the OpenShell supervisor is not running during the build. - Probe
-
A temporary container is created from the first-pass image. Inside this container,
which <binary>runs for each binary listed in the component’sprobe_binariesfield. Ifwhichfails, afind / -name <binary> -type f -executablesearch runs as a fallback. Each binary probe times out after 30 seconds. The total probe step times out after 5 minutes. - Pass 2
-
The policy is regenerated with the probed binary paths and any
runtime_globspatterns defined in the component YAML. The image is rebuilt with this corrected policy. Because all tool installation layers are cached from the first pass, only the policy COPY layer and later layers are rebuilt. The second pass typically completes in under 10 seconds.
The expected output looks like this:
Building OpenShell image: my-image:latest
Pass 1: building with stripped binary restrictions...
First pass: 45.2s
Probing binary paths in image...
Probe: 1.8s
WARNING: binary "uv" not found in image for component pkg_python
Pass 2: rebuilding with probed binary paths...
Second pass: 3.1s
When no tool-matched components need probing (for example, if only always: true components are present, or all components have explicit binaries fields), the build runs as a single pass without the probe step.
For details on how policy components define probe_binaries and runtime_globs, see Policy Components.
4. Verify the Image
cc-deck build verify --target openshell
The verify command runs checks inside the built image to confirm that expected tools are present.
5. Create a Workspace
After a successful build, create a workspace from the image:
cc-deck ws new my-sandbox --type openshell --image my-image:latest
The security policy is extracted automatically from the image. See OCI Policy Extraction for details on how extraction works.
Dual-Target Workflow
To maintain multiple target types from the same project, initialize with the targets you need:
cc-deck build init --target container,ssh
cc-deck build init --target container,openshell
cc-deck build init --target container,ssh,openshell
This scaffolds the artifact directories for each target side by side.
Capture your environment once:
/cc-deck.capture
Then build for each target separately:
/cc-deck.build --target container
/cc-deck.build --target ssh
/cc-deck.build --target openshell
All targets draw from the same shared manifest sections. Each backend generates its own artifacts (Containerfile, Ansible playbooks, or Containerfile with policy), but all install the same set of tools and configuration.
Detecting Manifest Drift
After modifying your manifest (adding a tool, changing a setting), you can check what would change before rebuilding:
cc-deck build diff
The diff command compares the current manifest against the last-generated artifacts and reports additions and removals. If both container and SSH artifacts exist, it reports drift for both targets.
You can also scope the diff to a single target:
cc-deck build diff --target container
cc-deck build diff --target ssh
Troubleshooting
Ansible is not installed
If you run /cc-deck.build --target ssh without Ansible installed, the command fails immediately with a message telling you to install it.
On macOS, install with brew install ansible.
SSH target is unreachable
When the remote host is unreachable, ansible-playbook fails with a connection error.
Claude reports the error but does not retry, because connectivity problems cannot be resolved by a playbook fix.
Check your SSH configuration, network access, and that the host is running.
Build retries exhausted
If the self-correction loop exhausts all 3 retry attempts, the command stops and reports the failing step with the error output. The generated artifacts (Containerfile or Ansible role files) remain in their last-edited state for manual inspection. Review the error, fix the artifact by hand, and re-run the build command.
Missing target section in manifest
If you run build for a target that is not configured in the manifest, the command fails with a message directing you to add the target section manually or re-run cc-deck build init --target <target>.
Conflict with previously generated artifacts
When you re-run /cc-deck.build after playbooks or a Containerfile have been modified, Claude shows a diff of the changed files and asks you to choose: use the newly generated content, keep the existing version, or stop.
This prevents accidental overwrites of manual edits.
Probe step fails
If the probe container cannot start or the probe commands fail, the build stops with an error.
The first-pass image is retained with a :probe-debug tag so you can inspect it:
podman run --rm -it my-image:probe-debug sh
which cargo
find / -name cargo -type f -executable
Common causes: the base image lacks which or find, or the container runtime cannot create containers from the first-pass image.
Binary not found during probing
When which and find both fail to locate a binary, the build logs a warning and continues:
WARNING: binary "uv" not found in image for component pkg_python
The tool’s policy entry receives only runtime glob patterns (if defined in the component YAML) and no exact path. This is normal for tools that are installed at runtime rather than at build time. If the tool should be present in the image, check that it is correctly installed in the Containerfile.
No two-pass output visible
If the build runs without showing "Pass 1" / "Probe" / "Pass 2" output, the single-pass path was used. This happens when no tool-matched components need probing, which means either:
-
The manifest has no tools that match any component’s
match.toolsfield -
All matched components already have explicit
binariesdefined in their YAML
To force two-pass behavior, ensure at least one tool in the manifest matches a component that has probe_binaries or match.tools defined.