Contents
A guide from Austin · November 2026
Your own always-on Claude, living on a MacBook
A MacBook that stays plugged in can be your own small server. Give it a handful of pieces and you get a Claude that is always on, has its own browser, can use your passwords, publishes websites under your own domain, builds iPhone and Mac apps, and answers you from the Claude app on your phone. This page explains the idea in plain words, then hands your Claude a build brief to carry it out.
What this is
Right now Claude lives inside a chat window. It can think, but it has nowhere to work: no computer of its own, no browser that stays logged in, no way to reach your accounts, nowhere to put a website. The idea here is to give it one. The computer is your MacBook. On it run a few small programs, each in its own sealed box (a Docker container), and the Mac itself does the Apple-only work.
I run the same shape at home. Nothing here is a product you have to buy from anyone; every piece is free or something you would pay for anyway (a domain, a Claude subscription, Apple's developer program if you ship apps). You do not need to be an engineer. Your Claude does the building; you do the parts only you can do, like signing in to accounts.
The pieces
Six parts. Four of them are containers on the Mac, one is a service in the cloud that makes the Mac reachable, and one is the Mac itself.
| Part | What it does | What it is |
|---|---|---|
| Password manager | Holds every login you own. You use it from the Bitwarden apps on your phone and laptop. Your Claude asks it for a password at the moment it needs one, so passwords never sit in files or chats. | Vaultwarden, a free, self-hosted server that the Bitwarden apps connect to. Your Claude gets its own Bitwarden account; you share with it only the logins you choose. |
| Browser | A Chrome that stays open and logged in. You sign in to your accounts in it once (bank, email, shops, anything with two-factor codes). Your Claude then drives that same browser to read your statements, download documents, or fill forms. | Chromium in a container, with a small web viewer so you can see it and click in it from any device. Your Claude talks to it through Chrome's own remote-control protocol. |
| Claude Code | The brain. A Claude that runs on your machine instead of in a chat window: it can run programs, edit files, use the browser and the vault, and build things. It shows up in the Claude app on your phone as a "machine" you can talk to from anywhere. | Anthropic's Claude Code in a container, signed in to your Claude Pro account, with its Remote Control listener running so the Claude app can reach it. |
| Tunnel | Connects your domain name to the Mac without opening anything on your home network. Your Claude's websites, the browser viewer and the vault each get a name like something.yourdomain.com, with a real certificate and a sign-in page in front of the private ones. | Cloudflare Tunnel (free). The Mac dials out to Cloudflare; Cloudflare hands visitors in. Works behind any apartment or hotel internet. |
| Websites | A place to publish. Your Claude can build a site and put it at a name under your domain in one step. | A small web server container. Each folder is a site; each site is a hostname. |
| The Mac itself | Everything Apple: building and signing iPhone and Mac apps, running the iPhone simulator, sending builds to TestFlight. And it is the thing your Claude can control remotely, the way I control my own Mac mini from anywhere. | Xcode, Apple's developer tools, and remote access (SSH plus Screen Sharing over a private network) so your Claude and you can reach the Mac when you are not in front of it. |
Two helpers sit alongside: Tailscale (free) is a private network between your devices so you can reach the Mac from anywhere as if you were at home, and GitHub (free) is where the code and the websites live, so a copy survives the Mac.
How it fits together
Day to day it feels like this. You open the Claude app on your phone and pick your machine. You say "log in to the electric company and tell me what I owe." Claude fetches the login from the vault, opens the site in the always-on browser, reads the page and answers. Or you say "make me a one-page site for the bake sale at bakesale.yourdomain.com," and a minute later it exists. Or "build the app and send it to TestFlight," and the Mac does it while you are out.
What you need and what it costs
| Item | Cost | Notes |
|---|---|---|
| A MacBook | you have it | Apple silicon, or an Intel model from 2019 or later with four cores. 16 GB of memory is the floor; 24 GB or more is comfortable with Xcode open. It stays plugged in and lid-closed, somewhere out of the way. An Intel Mac tops out at macOS Sequoia, which still runs the current Xcode; see Phase 9. |
| Claude Pro | your plan | Claude Code and Remote Control are included. Heavier daily use may push you to Max later; start with Pro. |
| A domain | about $10 a year | Buy it at Cloudflare Registrar so it is already on Cloudflare. Pick something you are happy to say out loud. |
| Cloudflare | free | The tunnel, DNS, certificates and the sign-in page in front of private things are all on the free plan. |
| Tailscale | free | Private network between your devices. Install the app on your phone and the Mac. |
| GitHub | free | Unlimited private repositories. Where everything your Claude builds is saved with history. |
| Docker Desktop | free for personal use | Runs the containers on the Mac. |
| Apple Developer Program | $99 a year, only if you ship apps | Needed to put apps on a real iPhone, on TestFlight, or in the store. The simulator works without it. |
| Backblaze (optional) | about $9 a month | Backs up the whole Mac. Time Machine to a small external drive also works. |
Ongoing: the domain and whatever you already pay for Claude. Everything else is free unless you choose the backup service or the developer program.
How the setup goes
This takes a few sessions, not an afternoon. The order matters: each step makes the next one possible.
- Sit at the MacBook for the first partThe first twenty minutes happen at the Mac. After that you can move to your phone.
- Start in a Claude chat on claude.aiPaste the address of this page and the message in section 8. Claude in the chat will read the brief and walk you through installing Claude Code on the Mac, one step at a time, in words.
- Open Terminal and install Claude CodeClaude in the chat tells you exactly what to paste. Terminal is the app where you paste commands; it looks plain and that is normal.
- Start Claude Code and point it at this page againOnce Claude Code is running on the Mac, you paste the same message to it. From here on, that Claude does the work on the machine; the chat window was just the bootstrap.
- Do the signups in one batchClaude will ask you, all at once, for the few things only you can do: buy the domain on Cloudflare, make a Tailscale account, make a GitHub account, and decide whether you want the Apple developer program now. About twenty minutes of clicking.
- Let it buildClaude sets the Mac to never sleep, installs Docker, writes the container setup, connects the tunnel, wires your domain, and checks its own work. It will tell you when it needs a click from you, for example an "allow" dialog on the Mac.
- Sign in on your phoneThe one step nobody can do for you: your Claude inside the box signs in to your Claude account, and you approve it on your phone. After that it stays signed in.
- Move your logins into the vaultInstall the Bitwarden app on your phone and laptop, point it at your own vault, and add your logins over time. Share with your Claude only the ones you want it to use.
- Sign in to the always-on browserOpen the browser viewer, log in to the sites you want Claude to be able to use, and do the two-factor step once. Those logins stick.
- Apple pipeline, when you want itClaude installs Xcode and, if you have joined the developer program, sets up signing and TestFlight. Your first app build goes out from the Mac while you watch from your phone.
What you can do afterward
- Talk to your Claude from anywhere. It appears in the Claude app under its own name. It remembers your projects because they live on the Mac.
- Hand it your logins without handing it your passwords. It asks the vault at the moment of use and never writes a password down.
- Let it read your accounts. Bank balances, bills, orders, statements: it uses the browser you already signed in to.
- Publish websites in a sentence. Any name under your domain, with a real certificate, and password-protected if you ask.
- Build iPhone and Mac apps. The Mac builds, signs and ships them; you test on your phone through TestFlight.
- Reach the Mac itself from anywhere. Screen Sharing over Tailscale from your laptop or iPad, and your Claude can run things on the Mac for you.
Things to know before you start
- Claude does not send messages or spend money on its own. Set that rule in writing in its instructions, as I do: it drafts, you approve. Nothing in this setup gives it a way around that.
- The vault is the crown jewels. Use a long master password you do not use anywhere else, turn on two-factor for your own account, and give your Claude its own account that only sees the logins you share with it. Your master password is never stored anywhere.
- Two-factor codes are still yours. When a site texts you a code, you type it, in the browser viewer, once. Claude cannot and should not read your texts.
- If the Mac sleeps, the box is off. The setup turns sleep off even with the lid closed. Keep it plugged in. After a power cut it comes back on its own, unless the disk is encrypted with FileVault, in which case it waits for you to type your password once at the machine. Claude will explain the choice.
- Backups. Code and sites go to GitHub automatically. The vault and the browser's logins live on the Mac, so a Time Machine drive or Backblaze is worth it.
- Privacy. The private parts (the browser viewer, the vault) are behind a sign-in page that emails you a code. Only the websites you choose to publish are public.
- It is yours. Every part is standard, free software on your own hardware under your own domain. Nothing phones home to me, and nothing here depends on me.
The message to paste to Claude
Copy this exactly. Paste it first into a Claude chat at claude.ai, and later into Claude Code on the Mac when the chat tells you it is installed. If a session ends partway, start a new one and paste it again; the brief tells Claude to look at what is already done and continue.
Read https://macbox.austinrenfroe.com/ all the way to the end. The second half,
"For your Claude: build brief", is written for you. Follow it from Phase 0. Work out
where you are running first. Bother me as little as possible: batch your questions,
run your own checks instead of asking me to verify, and explain things to me in plain
words. Never send a message, spend money or delete anything without asking me first.Questions
Reply to the email that brought you here. Austin has told his Claude it may answer your high-level questions about this setup by email, with him copied, and it will loop him in for anything beyond that.
Technician's section
For your Claude: build brief
If you are Claude reading this because a person pointed you at it: everything from here down is your operating brief. The person above it is your user. Read all of it before acting. The goal is one always-on MacBook running five containers (vault, browser, Claude Code agent, static web server, Cloudflare tunnel) plus the Mac's own Apple toolchain and remote access, built so the user never has to edit a file.
Operating principles
- Minimize interruptions. The user is not your QA. Batch every question. Phase 1 collects every account and token at once; after that you should not need them until the vault and browser sign-ins, and the Apple step.
- APIs and commands over clicks. Cloudflare's API, Tailscale auth keys, the GitHub CLI,
pmset,systemsetupanddefaults writeinstead of "open System Settings and click…". Where macOS forces a dialog (Docker's privileged helper, Tailscale's system extension, Screen Sharing permission), tell the user exactly what the dialog says and which button to press. - Verify yourself. After every phase, run a probe (a
curl, adocker compose ps, an SSH round trip) and report the result. Never ask "can you check whether that worked?" - Never destructive without an explicit yes. No
rm -rfof anything you did not create this session, no overwriting an existing config without a backup copy, no force-push, no deleting Cloudflare records you did not make. - Never send, never spend. You do not send email, texts or messages, buy anything, or enter payment details. You tell the user what to buy and wait.
- Secrets never go in chat, files you commit, or command lines where avoidable. Tokens go in
~/macbox/.env(mode 600) or the vault. Never print a token back. - Plain language on errors. Try two or three fixes yourself first. When you escalate, say what you tried and what you need in words, not stack traces.
- Track state. Keep a checklist in
~/macbox/STATE.mdas you go, one line per phase with done/pending and anything the next session must know. Sessions end; the file is how the next one resumes. - You are the engineer. Make routine choices yourself and name them afterward. Ask only when a choice turns on the user's taste, money or accounts.
Resuming
Before doing anything, find out where you are and what exists. Run the probe that fits, then say in one short message which phases are done and where you are picking up. Do not redo a finished phase; it would mean duplicate signups and duplicate records.
cat ~/macbox/STATE.md 2>/dev/null || echo "no state file: fresh start"
sw_vers 2>/dev/null && echo "running on the Mac" || echo "not on the Mac"
which brew docker tailscale gh claude xcodebuild 2>/dev/null
docker compose -f ~/macbox/compose.yaml ps 2>/dev/null
pmset -g | grep -E "^ +(sleep|disablesleep)"
Phases
Phase 0: where are you running?
Three possibilities. Decide from your own tools, not by asking.
- You are Claude in a chat window (claude.ai), with no shell. Your whole job is to get Claude Code installed on the Mac, then hand off. Walk the user through, one step per message, in words a non-engineer can follow:
- Sit at the MacBook. Open Terminal (press Command and Space, type Terminal, press Return).
- Paste this and press Return:
curl -fsSL https://claude.ai/install.sh | bash. If that fails, the current install instructions are at docs.claude.com/en/docs/claude-code/quickstart. - Close and reopen Terminal, type
claude, press Return, and sign in when it asks (it opens a browser; sign in with the same Claude account as the app). - Paste the message from section 8 into Claude Code. Tell the user the chat window's job is now done and the Claude on the Mac takes over.
- You are Claude Code on the Mac. Carry on to Phase 1.
- You are Claude Code inside the box's own container (hostname is the container's,
/workexists, nosw_vers). The build is done and you are the result. Read~/macbox/STATE.mdthrough the Mac over SSH if you need the history; otherwise you are in normal operation and this brief no longer applies.
Phase 1: collect everything in one batch
Give the user a single checklist, in this order, and wait for all of it. Explain each in one sentence. Store what comes back in ~/macbox/.env, created with mode 600.
- A domain on Cloudflare. Make a free Cloudflare account, then Domain Registration, Register Domains, buy one. Registering at Cloudflare means it is on Cloudflare already, nothing to point anywhere. Ask them for the domain name. Call it
DOMAIN. - A Cloudflare API token. Profile, API Tokens, Create Token, Create Custom Token, with permissions Zone: Zone: Read, Zone: DNS: Edit, Zone: Access: Apps and Policies: Edit, Account: Cloudflare Tunnel: Edit, Account: Access: Organizations, Identity Providers, and Groups: Read. Zone Resources: the one domain. Also note the Account ID from the domain's overview page. Store as
CF_API_TOKENandCF_ACCOUNT_ID. - Tailscale. Free account at tailscale.com, sign in with Google or Apple. Install the Tailscale app on the phone and sign in. In the admin console, DNS page: keep MagicDNS on and click Enable HTTPS. You will join the Mac yourself in Phase 2 through the Mac app; no key needed.
- GitHub. Free account. You will sign the Mac in with
gh auth loginin Phase 2; no token needed now. - Apple Developer Program, decision only. $99 a year. Needed for TestFlight, real devices and the App Store; not needed for the simulator. Ask yes or no, and if yes, ask them to enroll at developer.apple.com with their Apple ID (it can take a day to approve). Do not block on it; Phase 9 handles it.
- Two emails. The address they use for Claude, and the address they want the private sign-in page (Cloudflare Access) to send codes to. Usually the same.
- The Mac's login password, spoken once when you need
sudo. Never store it.
Phase 2: prepare the Mac
All of this runs in Terminal on the Mac. Use sudo -v once and a keepalive for the batch.
- Never sleep, lid closed.
sudo pmset -a sleep 0 disksleep 0 displaysleep 10 womp 1 autorestart 1andsudo pmset -a disablesleep 1. The second line is what keeps a MacBook awake with the lid closed and no external display. Check withpmset -g. Stand the Mac on its edge or on a stand so it can breathe; an Intel MacBook running Docker and Xcode with the lid closed gets warm. If the battery is flagged for service, tell the user to look once for a swollen battery (a trackpad that no longer clicks evenly, a bulging bottom case) and to replace it if so; a tired battery that still charges is fine for a machine that stays plugged in. - Come back after power loss.
autorestart 1above. Then the FileVault choice: if FileVault is on (fdesetup status), the Mac stops at the password screen after any reboot and the box stays down until someone types the password at the machine. Tell the user this plainly and recommend keeping FileVault on if the Mac is somewhere other people can reach, off if it lives in their home and they want hands-off restarts. Turn on automatic login for their account (sudo sysadminctl -autologin set -userName "$USER" -password -prompts; or System Settings, Users and Groups, Automatic login) only if FileVault is off. - Remote access on.
sudo systemsetup -setremotelogin on(SSH). Screen Sharing:sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -access -on -privs -all -users "$USER" -restart -agent; if macOS refuses, tell the user to switch on Screen Sharing in System Settings, General, Sharing. - Homebrew.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)", then addeval "$(/opt/homebrew/bin/brew shellenv)"to~/.zprofile. - Tools.
brew install gh git jq node;brew install --cask docker tailscale. Open Docker.app once (it asks for the privileged helper: the user clicks Allow and types their password); in Docker Desktop settings turn on Start Docker Desktop when you sign in. Open Tailscale.app, the user signs in; thentailscale statusshows the Mac. In the Tailscale admin console, disable key expiry on the Mac. - GitHub.
gh auth login(GitHub.com, HTTPS, log in with a web browser). The user pastes the one-time code in the browser. - Folders.
mkdir -p ~/macbox/{sites,work,data}.~/macboxis the box: compose file, env, data volumes, andwork/is where the agent's projects live. - Probe.
docker run --rm hello-world,tailscale ip -4,gh auth status,ssh localhost trueafter adding your own key. Record in STATE.md.
Phase 3: the containers
Write ~/macbox/compose.yaml with five services on one internal network. Match images to the Mac: on Apple silicon they are arm64, so use Debian's chromium package for the browser (Google Chrome has no Linux arm64 build); on an Intel Mac they are amd64 and either Chromium or Google Chrome works. In Docker Desktop, give the Docker VM 4 CPUs and 6 GB (Settings, Resources); on a 16 GB Mac leave the rest for macOS and Xcode. Pin image tags. Keep every volume under ~/macbox/data so backup is one folder.
services:
vault:
image: vaultwarden/server:1.34.1
restart: unless-stopped
environment:
DOMAIN: https://vault.${DOMAIN}
SIGNUPS_ALLOWED: "true" # flip to "false" after the two accounts exist
ADMIN_TOKEN: ${VAULT_ADMIN_TOKEN}
volumes: [./data/vault:/data]
browser:
build: ./browser
restart: unless-stopped
shm_size: 1g
environment: {VNC_PASSWORD_FILE: /run/secrets/vnc_pw}
volumes: [./data/chrome-profile:/profile]
secrets: [vnc_pw]
agent:
build: ./agent
restart: unless-stopped
environment:
CLAUDE_CONFIG_DIR: /home/owner/.claude
BW_SERVER: https://vault.${DOMAIN}
CDP_URL: http://127.0.0.1:9222 # the agent's own socat forwarder to browser:9222
volumes:
- ./data/claude:/home/owner/.claude # the sign-in survives rebuilds
- ./data/bw:/home/owner/.config/bw
- ./work:/work
- ./sites:/sites
- ./data/agent-ssh:/home/owner/.ssh
extra_hosts: ["mac:host-gateway"] # the Mac itself, for SSH
sites:
image: nginx:1.27-alpine
restart: unless-stopped
volumes:
- ./sites:/usr/share/nginx/sites:ro
- ./sites.conf:/etc/nginx/conf.d/default.conf:ro
cloudflared:
image: cloudflare/cloudflared:2025.8.0
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${CF_TUNNEL_TOKEN}
secrets:
vnc_pw: {file: ./data/vnc_pw}
browser/Dockerfile: debian:bookworm-slim with chromium xvfb openbox x11vnc novnc websockify fonts-liberation fonts-noto-color-emoji tini. The start script runs Xvfb on :1 (1440x900), openbox, then Chromium not headless (so the viewer shows a real window and logins with two-factor prompts work) with --user-data-dir=/profile --remote-debugging-port=9223 --no-first-run --password-store=basic, then x11vnc -display :1 -forever -shared -rfbport 5900 -passwdfile /run/secrets/vnc_pw -localhost and websockify --web /usr/share/novnc 6080 localhost:5900. Two traps worth knowing. A headed Chromium only listens for remote debugging on its own loopback, so the container also runs socat TCP-LISTEN:9222,bind=0.0.0.0,fork,reuseaddr TCP:127.0.0.1:9223 with Chromium on 9223; and Chromium refuses debugging requests whose Host header is a name like browser, so the agent runs its own forwarder (socat TCP-LISTEN:9222,bind=127.0.0.1,fork TCP:browser:9222) and talks to http://127.0.0.1:9222. Chromium's sandbox needs user namespaces inside the container: run as a non-root user with a seccomp profile that allows them, and fall back to --no-sandbox only if it will not start, saying so in STATE.md. Nothing publishes 9222 to the Mac. Port 6080 is the viewer, published only to the tunnel.
agent/Dockerfile: debian:bookworm-slim, a non-root user owner (uid 1000), git python3 jq curl openssh-client ca-certificates, Node LTS, then npm install -g @anthropic-ai/claude-code @bitwarden/cli chrome-devtools-mcp. Entrypoint: a small supervisor script that (1) if ~/.config/bw/master.pw exists, runs bw unlock --passwordfile ~/.config/bw/master.pw --raw into ~/.config/bw/session and starts bw serve --hostname 127.0.0.1 --port 8087 in the background; (1b) starts the CDP forwarder socat TCP-LISTEN:9222,bind=127.0.0.1,fork TCP:browser:9222; (2) for every git repository directly under /work, keeps one claude remote-control --name <folder> --spawn same-dir --permission-mode auto running from inside that folder, restarting it if it exits, and re-scans /work every two minutes for new folders. A sleep infinity at the end keeps the container up even before sign-in.
sites.conf: one nginx server block with server_name ~^(?<site>[a-z0-9-]+)\. and root /usr/share/nginx/sites/$site; so a folder named bakesale answers at bakesale.DOMAIN, plus a 404.html fallback. Put a one-page placeholder site in sites/www so the apex has something to show.
Then docker compose build and docker compose up -d. Probe: docker compose ps shows five services up; docker compose exec agent curl -s http://127.0.0.1:9222/json/version returns Chromium's version; docker compose exec agent claude --version prints a version.
Phase 4: the tunnel and the domain
- Create a remotely-managed tunnel with the API (
POST /accounts/{CF_ACCOUNT_ID}/cfd_tunnel, namemacbox,config_src: cloudflare). Save the returned token asCF_TUNNEL_TOKENin.envanddocker compose up -d cloudflared. - Set the tunnel's ingress (
PUT .../cfd_tunnel/{id}/configurations):vault.DOMAINtohttp://vault:80;browser.DOMAINtohttp://browser:6080;DOMAINand*.DOMAINtohttp://sites:80; catch-all 404. - DNS: CNAME records for
@,*,vaultandbrowserpointing at{tunnel-id}.cfargotunnel.com, proxied. The wildcard is what lets a new site exist with no DNS step. - Cloudflare Access in front of the private hostnames: in Zero Trust, add the One-time PIN login method; create a self-hosted application for
browser.DOMAINwith a policy allowing only the user's email. Do the same for the vault's admin page only (vault.DOMAIN/admin); the vault itself stays reachable by the Bitwarden apps, which cannot pass an Access login. Vaultwarden is built to face the internet: the user's account gets a long master password and two-factor in Phase 6, and the tunnel hides the home IP. - Probe from outside the Mac's network if you can (a phone off wifi) or at least through Cloudflare:
curl -sI https://DOMAINis 200,curl -sI https://browser.DOMAINis a 302 to the Access login,curl -s https://vault.DOMAIN/alivereturns a timestamp.
Phase 5: sign Claude in and make it appear in the app
docker compose exec -it agent claude. It prints a sign-in URL. Tell the user to open it on their phone and approve. The credentials land indata/claudebecause ofCLAUDE_CONFIG_DIR, so they survive rebuilds.- Make a first project:
mkdir /work/notes && cd /work/notes && git initinside the agent, and aCLAUDE.mdat/work/CLAUDE.mdwritten for this user (see Phase 8). The supervisor picks the folder up and starts its Remote Control listener. - Register the MCP servers in the agent's user scope:
claude mcp add --scope user chrome -- chrome-devtools-mcp --browserUrl http://127.0.0.1:9222. Confirm withclaude mcp list. - Probe: the user opens the Claude app, Code tab, and sees a machine named
notes. They send "what folder are you in?" and get/work/notes. Record it.
Phase 6: the vault
- Tell the user to open
https://vault.DOMAINon their laptop, create their account with a long master password they use nowhere else, then in Settings, Security, Two-step Login turn on an authenticator app. Then create a second account for Claude (for exampleclaude@DOMAINif they set up email, or any address they control) with its own master password. - Make an Organization from the user's account, invite the Claude account, and explain the rule: anything in the organization's collection is visible to Claude; anything in the user's personal vault is not. The user moves logins into the collection one at a time as they decide to.
- Set
SIGNUPS_ALLOWED: "false"and restart the vault. - In the agent:
bw config server https://vault.DOMAIN, thenbw loginas the Claude account (the user types that account's email and master password into the prompt; you do not see it). Then write the Claude account's master password to~/.config/bw/master.pw(mode 600) so the supervisor can unlock on every start. State the tradeoff in one sentence: this file lets the box unlock Claude's vault unattended; it never holds the user's own master password. - Restart the agent and probe:
curl -s http://127.0.0.1:8087/statusinside the agent says unlocked; a test item placed in the collection comes back fromcurl -s 'http://127.0.0.1:8087/list/object/items?search=test'. - Phone and laptop: install the Bitwarden apps, tap the region selector, choose Self-hosted, enter
https://vault.DOMAIN, sign in as the user.
Phase 7: the browser
- Set the viewer password: write a random one to
data/vnc_pwand tell it to the user once (or store it in their vault). Restart the browser service. - The user opens
https://browser.DOMAIN, passes the Access email code, enters the viewer password, and sees Chromium. They sign in to whatever sites they want Claude to use and complete two-factor there. Cookies live on thechrome-profilevolume, so they persist. - Probe from the agent: ask Claude (in the app) to open
https://example.comthrough the chrome MCP and report the title. Then ask it to open a site the user signed in to and read something harmless (the account's display name). - Write the house rules into
/work/CLAUDE.md: Claude reads and navigates freely; it never submits a payment, changes a password, sends a message or deletes anything through the browser without the user saying so in that conversation.
Phase 8: the Mac from inside the box, and from anywhere
- In the agent,
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""; append the public key to the Mac user's~/.ssh/authorized_keys. The compose file mapsmacto the host, sossh <macuser>@mac sw_versfrom the agent is the probe. Add an~/.ssh/configentry so it is justssh mac. - This is how the box controls the Mac:
ssh mac xcodebuild …,ssh mac osascript -e '…'for application scripting,ssh mac screencapture -x /tmp/shot.pngthenscpit back to look at the screen,ssh mac open -a Safari https://…. Write a short section in/work/CLAUDE.mdlisting these so future sessions know the Mac is reachable and how. - For the user: on the laptop or iPad, with Tailscale signed in, Finder's Go, Connect to Server,
vnc://<mac-tailscale-name>opens Screen Sharing from anywhere. On the phone, any VNC client does the same. Tell them the name. - Install Claude Code natively on the Mac too (it already is, from Phase 0). A
claude remote-control --name mac --spawn same-dir --permission-mode autorunning in~/macbox/work-macas a launchd user agent (~/Library/LaunchAgents/com.macbox.claude-rc.plist, KeepAlive) gives the user a second machine in the Claude app, "mac", that runs on macOS itself with Xcode in reach. The container is the everyday box; the Mac listener is for Apple work and for fixing the box if the containers are down. /work/CLAUDE.mdfor this user, written in their words: who they are, what the box is, the never-send-never-spend rule, how to fetch a login (bw-serveon 127.0.0.1:8087), how to use the browser (chrome MCP), how to reach the Mac (ssh mac), how to publish a site (a folder under/sitesis live at<folder>.DOMAINat once), and to commit work to GitHub withgh repo create --privatefor each new project.
Phase 9: the Apple pipeline
- Xcode. First check the macOS version:
sw_vers. Xcode 26 needs macOS 15.6 or later; an Intel Mac that cannot go past Sequoia (15) still runs it, so update macOS fully before installing. Newer Xcode releases will require macOS Tahoe, which Intel Macs cannot run; tell the user plainly that an Intel Mac ships apps on the current SDK for roughly another year and a half before Apple's submission rules outgrow it. Thenxcodesvia Homebrew or the App Store; thensudo xcode-select -s /Applications/Xcode.app,sudo xcodebuild -license accept,xcodebuild -runFirstLaunch, and install the iOS platform (xcodebuild -downloadPlatform iOS). This is several gigabytes; run it in the background and keep going. Probe:xcrun simctl list devices available | headshows iPhones, and a one-file Swift app builds for the simulator. - If the user joined the Developer Program. In App Store Connect, Users and Access, Integrations, make a Team API key (role App Manager); the user downloads the
.p8once and gives you its path, key ID and issuer ID. Store the key under~/macbox/data/asc/(mode 600) and the IDs in.env. Create a distribution certificate and the app's bundle ID from the Mac withxcodebuild -allowProvisioningUpdatesand the API key, so signing is automatic (-authenticationKeyPath … -authenticationKeyID … -authenticationKeyIssuerID …). - A ship script at
~/macbox/bin/ship-ios <project-dir>: bump the build number,xcodebuild archive,xcodebuild -exportArchivewith methodapp-store-connect,destination uploadanduploadSymbols, which uploads straight to App Store Connect; TestFlight processes it from there (no notarization step for iOS). A siblingship-macdoes archive,-exportArchivewith Developer ID,xcrun notarytool submit --wait,xcrun stapler staple. Both scripts print one line per phase and exit non-zero on failure. - Testers. Add the user's own Apple ID as an internal tester; the TestFlight app on their phone gets the build. Probe: a hello-world app appears in TestFlight on the user's phone.
- The agent container can trigger all of this:
ssh mac ~/macbox/bin/ship-ios /Users/<user>/macbox/work-mac/<app>. Keep iOS projects in~/macbox/work-macon the Mac (Xcode needs the real filesystem), not in the container's/work.
Phase 10: keep it alive
- Restart policy is already
unless-stopped, and Docker Desktop starts at login, which with automatic login means a reboot brings everything back. - A watchdog as a launchd user agent every five minutes:
docker compose psmust show five services running,curl -s http://localhost:6080must answer, and the Mac must have been awake continuously (pmset -g log | grep -c Sleepnot increasing). On failure,docker compose up -dand write to~/macbox/data/watchdog.log. - Tell the user when it breaks. A free healthchecks.io check that the watchdog pings on every green run; healthchecks emails the user if the pings stop. This is the only external alerting and it is one
curl. - Backups.
~/macbox/dataholds the vault, the browser profile, Claude's sign-in and the Bitwarden session: back it up. Time Machine covers it if a drive is attached; otherwise recommend Backblaze and leave the choice to the user. Everything underwork/andsites/should also be in private GitHub repositories; make the first commit and push for each. - Updates. Once a month:
docker compose pull && docker compose build --pull && docker compose up -d, andclaude updateon the Mac. Put a reminder in STATE.md; do not automate it unattended.
Final verification
Run every line and show the user the result as a plain checklist, pass or fail. Fix every fail before calling it done.
- The Mac has
disablesleep 1and has been up for more than an hour with the lid closed. docker compose ps: five services, all running, all with a restart policy.https://DOMAINserves the placeholder site with a valid certificate; a new foldersites/testis live athttps://test.DOMAINwithin a minute with no other step.https://browser.DOMAINasks for an email code, then a viewer password, then shows Chromium with the user's logins intact after adocker compose restart browser.- The Claude app shows machines named
notesandmac; each answers a message. - From the app, Claude fetches a test login from the vault without the user typing anything, and reads a page in the browser that required that login.
- From the app, Claude runs
sw_verson the Mac throughssh macand takes a screenshot of the Mac's screen. - Screen Sharing to the Mac works from the user's phone or laptop over Tailscale with wifi off.
- If the Developer Program is active: a hello-world build reached TestFlight on the user's phone, started from the app.
- The healthchecks.io check is green; stopping the watchdog for ten minutes makes it email the user; restarting makes it green again.
~/macbox/STATE.mdsays every phase is done, and lists the two or three things the user must do by hand after a power cut.
When you are stuck for good: write what you tried to ~/macbox/STATE.md under a heading "Stuck", tell the user in plain words what the blocker is, and ask them to start a fresh Claude Code session with the same paste. The next session reads STATE.md first.