Appearance
Set up a Coder workspace
Give each person a Coder workspace that works like a developer's own machine: Docker, DDEV, bakers.build, herdr, the agent CLIs and the tunnel clients are ready, and you sign in once.
The bakers-ddev template provides one long-lived workspace per person. It starts without projects. Inside it, /bb-init signs you in, checks out your projects and starts one conductor per checkout. For a plain server instead, see Set up a worker server.
You need: a Coder deployment whose Docker host runs the Sysbox runtime on amd64 and can pull the workspace image, permission to push templates, a bakers.build account with a workspace rank, the control-plane URL, and Claude Code and Codex accounts.
Push the template from the
coder/bakers-ddevdirectory of the bakers.build repository. Its default image is not public: pass your own with--variable image=<image>. The template has no registry credentials, so the Docker host must be able to pull the image itself. The README lists the image requirements and the variables.shcoder templates push bakers-ddev -d coder/bakers-ddevCreate your workspace and open a terminal in it.
shcoder create <name> --template bakers-ddev coder ssh <name>The first start installs
bakers,herdr,claude,codex,cursor-agent,gh,glab,cloudflared,frpcandssinto your home volume, and startsherdr server. The log is/tmp/coder-startup-script.log.Sign in to Claude Code, add the bakers.build plugin and run
/bb-init. Claude prints a URL to open in your own browser.shclaude auth login claude plugin marketplace add https://bakers.build/marketplace.json claude plugin install bakers@bakers mkdir -p ~/projects && cd ~/projects && claudeTrust the folder when Claude asks, so the worktrees below it are trusted too; otherwise the first agent run waits at that question in its herdr pane. Then type
/bb-init. It walks you through steps 4 to 9 one at a time, checks each one and writes configuration only after you confirm. The steps below are what it does, for reference or to do by hand.Sign in to bakers.build. An unattended conductor needs a machine consumer that an operator issues (connections); give its secret to this command only.
bakers initwrites it into a private file under~/.bakers.shBB_CLIENT_SECRET='<issued secret>' bakers init --scope all --machine-id <name> --user-id <operator> \ --base-url <server-url> --auth-type client_credentials --client-id <id> --secret-env BB_CLIENT_SECRET --yes bakers dropsh auth loginKeep only the installed harnesses,
@bakers-build/addon-claudeand@bakers-build/addon-codex, inaddonsof~/.bakers/machine.config.js(machines), and set both tosecurity: 'custom'. The defaultsandboxcannot start in the workspace: itsbwrapneeds unprivileged user namespaces, which a Docker host on Ubuntu 23.10 or later restricts. The container is the boundary instead, so a run can still read everything in your home, including your sign-ins and SSH key (Run security).js{ use: '@bakers-build/addon-claude', with: { security: 'custom', config: { permissions: { defaultMode: 'dontAsk', allow: ['Bash', 'Edit(./**)'], deny: ['Read(~/.ssh/**)', 'Read(~/.bakers/**)'] }, disableClaudeAiConnectors: true, }, }, }, { use: '@bakers-build/addon-codex', with: { security: 'custom', config: { sandbox_mode: 'danger-full-access', approval_policy: 'never' } }, },dontAskdenies whatallowdoes not list instead of asking, so an unattended run never waits at a question. Thedenyrules cover Claude's Read tool only, not shell commands.Sign in to Codex. It prints a URL to open in your own browser.
shcodex login --device-authSign in to GitHub and GitLab. Both CLIs show a one-time code and a URL to open in your own browser; one sign-in covers every repository on that host.
shgh auth login --git-protocol https gh auth setup-gitOn your own GitLab,
glabsigns in through an OAuth application of that instance. An administrator creates it once under Admin > Applications: redirect URIhttp://localhost:7171/auth/redirect, Confidential off, Device authorization grant on, scopesopenid,profile,read_user,write_repositoryandapi. Its application ID is theclient_id:shglab config set client_id <application-id> -g --host <host> glab auth login --hostname <host> --device --git-protocol sshWithout the OAuth application,
glab auth login --hostname <host>with a personal access token works too.Create an SSH key, add its public key to your Git host and record the host's key. Project repositories with SSH URLs, and an
after_createthat clones over SSH, need both: an unattended clone cannot accept an unknown host key.shssh-keygen -t ed25519 cat ~/.ssh/id_ed25519.pub ssh-keyscan <git-host> >> ~/.ssh/known_hostsChoose a transport for previews: add the
@bakers-build/addon-portsentry toaddonsin~/.bakers/machine.config.jsand keep the file's other fields (Shared service tunnels). The settings are described on the transport pages: Cloudflare Quick, Named Cloudflare and frp.Cloudflare Quick needs nothing else:
js{ use: '@bakers-build/addon-ports', with: { transport: { use: '@bakers-build/addon-ports-cloudflare-quick' } } },Named Cloudflare needs the connector token in
~/.cloudflared/tunnel-token. The workspace runscloudflared tunnel runon every start while that file exists:js{ use: '@bakers-build/addon-ports', with: { transport: { use: '@bakers-build/addon-ports-cloudflare', with: { zone: 'preview.example.org', accountId: '<account id>', tunnelId: '<tunnel id>', zoneId: '<zone id>', apiToken: '<api token>', }, }, }, },frp needs the client config in
~/.config/frp/frpc.json. The workspace runsfrpcon every start while that file exists. Create the directory of itsincludesentry first, orfrpcrefuses to start, and keeploginFailExitfalse: nothing restartsfrpc, and by default it ends for good when the server is unreachable at start.shmkdir -p ~/.bakers/frp ~/.config/frpjson{ "serverAddr": "frps.example.org", "serverPort": 7000, "user": "bakers-<name>", "loginFailExit": false, "auth": { "method": "token", "token": "<frps token>" }, "transport": { "tls": { "enable": true, "trustedCaFile": "/home/coder/.config/frp/ca.pem", "serverName": "frps.example.org" } }, "webServer": { "addr": "127.0.0.1", "port": 7400, "user": "bakers", "password": "<admin password>" }, "includes": ["/home/coder/.bakers/frp/routes.json"] }js{ use: '@bakers-build/addon-ports', with: { transport: { use: '@bakers-build/addon-ports-frp', with: { zone: 'preview.example.org', clientConfigPath: '/home/coder/.config/frp/frpc.json', adminPort: 7400, adminUser: 'bakers', adminPassword: '<admin password>', }, }, }, },Restart the workspace once so the connector starts, or start it by hand, for example
nohup frpc -c ~/.config/frp/frpc.json >/tmp/frpc.log 2>&1 &. The secrets in this file stay in your home volume.Check out your projects and start their conductors. One conductor serves one checkout (Conductors).
/bb-initlists your projects, clones each chosen one from its repository at its default branch into~/projects/<key>, configures it withbb-init-projectand, after you confirm, starts its conductor in its own herdr tab. By hand:shgit clone --branch <default-branch> <repository-url> ~/projects/<key> cd ~/projects/<key> bakers oven start --foregroundVerify.
shherdr workspace list bakers config check --scope connection --online bakers oven status
Done when: herdr workspace list succeeds, the online check has no findings and each conductor shows online. Continue with Conductors.
See the workspace in your own herdr
Add it as a saved machine on your own computer: herdr machine add <name>.coder --label <name>. herdr verifies SSH host keys strictly, while coder config-ssh sends the keys of *.coder hosts to /dev/null, so pin the workspace's key first with a block above Coder's managed section in ~/.ssh/config. The key stays the same across workspace restarts.
Host <name>.coder
UserKnownHostsFile ~/.ssh/known_hosts
StrictHostKeyChecking accept-newAfter a workspace restart
Everything under your home survives: the tools, the sign-ins, the machine config and your projects. The container is new, so the start log shows every tool as skipped, and the workspace starts herdr server and any connector whose config file exists. Conductors and DDEV projects do not come back by themselves. Run ddev start and bakers oven start --foreground again in each checkout, in a herdr tab, and share a preview again with bakers ports up.
Update a tool
A tool that is already installed is skipped, so a new version in the template reaches a workspace only when the tool is missing. Update one yourself with its own updater, for example claude update or bakers update, or delete it (rm "$(command -v <tool>)") and restart the workspace.