develop is a local Docker Compose workspace with a separate control plane.
The workspace runs your code and tools. develop-control keeps GitHub,
Cloudflare, and Codex auth out of the workspace and handles the operations that
need them.
-
Copy the example config:
cp container/.env.example container/.env.local
-
Edit
container/.env.localfor your domain, tunnel name, ports, and Cloudflare Access email. -
Start the stack:
bash scripts/dev-up.sh
-
Open the local config UI printed by the script, then paste GitHub and Cloudflare tokens there if you want GitHub access or tunnel provisioning.
-
Connect to the workspace:
bash scripts/dev-ssh.sh
Run the local, non-destructive checks with:
bash scripts/test.shSet CW_TEST_AUDIT=1 to include npm audit for the MCP package.
develop-dev: the workspace container. It runs SSH on port2222inside the container and exposes the app port configured byCW_APP_PORT.develop-control: the local config UI and control plane. It stores auth files, brokers GitHub and Cloudflare operations, and can provision the Cloudflare tunnel.
Tracked files should not contain real tokens, passwords, private keys, or runtime metadata. The repo ignores:
.container-data/.container-runtime/container/.env.local.codex/,.claude/, and.agents/secrets/container/secrets/
Runtime files are generated or saved under .container-data/control/, for
example:
admin-password.txtgithub-token.txtcloudflare-api-token.txtcloudflare/app-tunnel-token.txtstate/codex/
Compose mounts these files into develop-control and passes file paths through
environment variables. Keep raw token values out of compose.yml and committed
env files.
The config UI writes the GitHub token, Cloudflare API token, and tunnel token files so you can rotate them or reprovision without rebuilding. The admin password and managed SSH private key are mounted read-only.
The workspace includes a gh wrapper. Commands such as gh repo view,
gh pr list, gh issue create, and gh api are forwarded to
develop-control, where the real GitHub CLI runs with the saved token.
Normal GitHub Git operations go through an internal smart HTTP proxy on
develop-control. The generated gitconfig rewrites GitHub and Gist HTTPS and
SSH URLs to that proxy, so git clone, git fetch, and git push still work
without storing a GitHub token in ~/.gitconfig, ~/.config/gh, GH_TOKEN,
GITHUB_TOKEN, or a Git credential helper.
The workspace can use GitHub through that proxy, but it does not receive the underlying credential. That keeps the secret out of the workspace. It does not change what already-running workspace code can do with the Git access granted by the control-plane token.
Use a Cloudflare API token scoped to the configured account and zone. The control UI validates the token before saving it and warns if it looks broader than needed.
Minimum capabilities used by this stack:
- Cloudflare Tunnel write access
- Cloudflare Access application and policy write access
- DNS write access for the configured zone
The account id is resolved from CW_CLOUDFLARE_ZONE_NAME; users normally do not
need to configure it separately.
The SSH setup script includes the generated client private key, so
/api/ssh/setup-script requires both:
- the control-plane admin password
- a Cloudflare Access JWT for the config app
The control plane accepts either the Cf-Access-Jwt-Assertion header or the
CF_Authorization cookie, verifies it against the Access public keys for the
configured team domain, and checks the JWT audience against the config app AUD.
Direct local access can still load the dashboard shell, but config details are
redacted and it cannot export the managed SSH key.
When provisioning succeeds, the control plane saves the config app AUD and team
domain. If the UI says the Access verifier is missing metadata, re-provision the
tunnel or set these in container/.env.local:
CW_CLOUDFLARE_ACCESS_TEAM_DOMAIN=https://your-team.cloudflareaccess.com
CW_CLOUDFLARE_ACCESS_CONFIG_AUD=your-config-app-audUse container/.env.local for machine-specific non-secret settings. The file is
ignored by git. For one-time token seeding, use the config UI. If automation
needs to seed a token from the shell, export it for a single
scripts/dev-up.sh run and unset it afterward.
bash scripts/dev-down.sh