moon-docker-dev
Skill1 starsChanged 18 days agoApache-2.0View on GitHub
Containerizes local development for a moonrepo + proto monorepo with Docker Compose: a proto-based dev image pinned by .prototools, a bind-mounted checkout with dependencies and caches in Docker volumes, one compose project per git worktree, and portless hostnames (https://<app>.main.<repo>.localhost, https://<app>.<worktree>.<repo>.localhost) routed to Docker-assigned ports by moon tasks (compose-up / compose-down) that run it detached. Use when the user asks to containerize, dockerize, or add docker-compose / Docker local dev to a moon or moonrepo repository, or to run dev containers per worktree without port conflicts.
- Reads credentials
In this repository20 other files it gives its agents.
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- openspec-update-changeSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- openspec-update-changeSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- openspec-update-changeSkill
- repo-docsSkill
- vietqr-quick-linkSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 21
Read these before writing anything:
- Very specific: line 31
No fixed host ports. Each service publishes 127.0.0.1::<container port> (Docker picks a free loopback port).
- Very specific: line 52
Add "portless": "^<proxy version>" to the root package.json devDependencies (check the running proxy with portless --version, or use the …
- Very specific: line 87
Add .env to .gitignore (the example stays tracked).
- Very specific: line 103
Ports change on every recreate: always re-alias with --force after compose up.
- Distinctive: line 107
Run alongside any existing host dev server or other containers.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
moon + Docker Compose local dev
Sets up etc/docker/ (or the repo's equivalent tooling folder) so moon run <tool>:compose-up starts each app's dev server in a container against the checkout, reachable at a stable HTTPS hostname, and several git worktrees can run side by side.
Gather first
Read these before writing anything:
.prototoolsand.moon/workspace.yml(project globs; every folder matched by a glob becomes a moon project, even withoutmoon.yml)..moon/toolchains.ymland the package manager (Yarn version,nodeLinker,yarnPath).- Each app to containerize: its moon project id, dev task (
moon project <id>), container port, and the folders its dev server writes (build caches, output, nestednode_modules). - Where the repo keeps tooling tasks (an existing
scripts/tool project with zx/tsx scripts). Put the compose tasks there; do not create a moon project just for Docker. id -u/id -gon the host,docker compose version,git worktree list, and whether a portless proxy already runs (ps -eo args | grep 'portless proxy',~/.portless/).
Routing (portless)
No fixed host ports. Each service publishes 127.0.0.1::<container port> (Docker picks a free loopback port); compose-up reads it with docker compose port and registers it with portless alias <route> <port> --force. The portless proxy on the host serves the route over HTTPS with its trusted local CA.
| Checkout | Compose project | URL |
| main | <repo>-main | https://<app>.main.<repo>.localhost |
linked worktree in folder bk | <repo>-bk | https://<app>.bk.<repo>.localhost |
The worktree name is main when git rev-parse --git-dir equals --git-common-dir, otherwise the sanitized worktree folder name (stable across branch switches). WORKTREE from the shell or etc/docker/.env overrides it. portless is a root devDependency, called as yarn portless ... from the repo root, so the CLI version is pinned per repo.
Workflow
- [ ] 1. Add portless as a root devDependency (match the host proxy's major/minor)
- [ ] 2. Copy and fill the templates (placeholders below)
- [ ] 3. Wire the compose tasks into the tooling project's moon.yml
- [ ] 4. Git-ignore `.env`; document usage and the proxy prerequisite (README + docs)
- [ ] 5. Verify (checklist below)1. portless devDependency
Add "portless": "^<proxy version>" to the root package.json devDependencies (check the running proxy with portless --version, or use the latest if none runs) and yarn install. The package has no install scripts, so CI installs are unaffected.
2. Templates
Copy from templates/ and replace the placeholders:
| Template | Destination |
Dockerfile, Dockerfile.dockerignore | etc/docker/ |
docker-compose.yml | etc/docker/ |
env.example | etc/docker/.env.example |
compose-env.ts, compose-up.ts, compose-down.ts | the tooling project's scripts folder, side by side (for example etc/scripts/moon-tasks/); if the helper goes elsewhere, fix the ./compose-env imports |
moon-tasks.yml | merge into the tooling project's moon.yml, adjusting $projectRoot/moon-tasks/ to the scripts folder |
Placeholders:
__REPO__: compose project prefix and last route label (repo name, lowercase).__APP__: moon project id, compose service name and route label.__APP_DIR__: app folder relative to the repo root (for exampleapps/site).__APP_PORT__: port the dev server listens on inside the container.
Adapt per stack:
- Volume mount points: root
node_modules,.moon/cache, the package manager cache, and every folder the app's dev server writes (for Gatsby:.cache,public, nestednode_modules). List the same paths in the Dockerfilemkdir, the composevolumes, andvolumeMountPointsincompose-up.ts. - The dev server must bind
0.0.0.0: pass it through moon withmoon run __APP__:dev -- --host 0.0.0.0 --port __APP_PORT__(adjust flags to the framework) instead of editing the app's task. - Non-Yarn package managers: drop
YARN_INSTALL_STATE_PATHand the Yarn cache volume, install the right tool with proto, and call portless through that package manager. - More apps: add a service per app with its own cache volumes and an entry in
servicesincompose-env.ts;compose-upregisters and prints every service. - Raw TCP services (databases): keep the Docker-assigned loopback port and print the connection string instead of a portless route.
3. moon tasks
Both tasks run detached-friendly zx scripts and stay out of CI (runInCI: false). compose-up uses the utility preset because docker compose up --detach returns.
4. Ignore and docs
Add .env to .gitignore (the example stays tracked). Document moon run <tool>:compose-up / compose-down, the URLs, and that the portless proxy must run on the host (yarn portless service install once, or yarn portless proxy start).
Pitfalls (each one bit a real setup)
- Root-owned folders on the host: Docker creates a missing volume mount point inside a bind mount on the host, owned by root.
compose-upmust create every mount point beforedocker compose up; plaindocker compose upon a fresh clone breaks host tooling. - UID 1000 already taken:
ubuntu:nobleships userubuntuwith UID 1000. Remove it before creatingdevwith the host UID. - Versions only from
.prototools: install node, the package manager and moon with plainproto install <tool>after copying.prototools; no per-build version overrides. Install only what the services need (not Rust, Go, Dagger, ...). - Shared install state: Yarn writes
.yarn/install-state.gzinto the checkout; pointYARN_INSTALL_STATE_PATHinto thenode_modulesvolume. - Build context: the context is the repo root for
COPY .prototools;Dockerfile.dockerignore(next to the Dockerfile) keeps it to.prototoolsonly. - Empty YAML mappings: lint rules may reject
volume_name:; usevolume_name: {}. - Do not clean a running dev server's cache: production builds that run a clean step (for example
gatsby clean) break a dev server using the same folders; that is why caches live in volumes. - Branch switches under a running dev server: switching to a branch that lacks files the server watches can leave it broken (for example a page that stops being a page) even after switching back; restart the dev server.
- New tool releases: proto resolves ranges like
moon = "^2"to the newest tag, which can briefly have no published binaries (404 on install). It resolves itself once the release finishes; do not add overrides for it. - git in a linked worktree: its
.gitis a file pointing at<main checkout>/.git/worktrees/<name>by absolute host path, so moon fails withnot a git repositoryin the container. Mount the git common dir (git rev-parse --path-format=absolute --git-common-dir) at the same path, passed asGIT_COMMON_DIR. - portless aliases get no automatic worktree label: that only applies to apps portless starts itself (
portless run, as a branch prefix); build the route name in the helper. - One proxy, many repos: the repo's portless CLI writes routes the host proxy reads; keep the devDependency on the proxy's major/minor. Hostnames come from the proxy, so read the public URL from
yarn portless listrather than assuming port 443. - Ports change on every recreate: always re-alias with
--forceaftercompose up; an unclean stop leaves a stale alias (gateway error) until the nextcompose-up.
Verify
Run alongside any existing host dev server or other containers; never stop the user's running containers, dev servers or proxy without asking.
docker compose -f etc/docker/docker-compose.yml configrenders<repo>-mainand a127.0.0.1port with no fixed host port;WORKTREE=xrenders<repo>-x.- The helper returns
mainin the main checkout and the folder name for a linked worktree (call it with that worktree's directory); overrides are sanitized. moon run <tool>:compose-upreturns,yarn portless listshows each route, and it prints the HTTPS URLs and the logs command; the container runs as the host UID with the.prototoolsversions.- Over HTTPS: main routes return 200 and an unknown URL returns 404; an edit on the host shows up in the container through the proxy (live reload).
- Running
compose-upagain keeps the same URL even though the container's host port changed. compose-upfrom a linked worktree starts the service too (moon can run git there) and serves the worktree's URL.git statusshows no unexpected files andfind . -user root(excludingnode_modules,.git) finds nothing; the host's own dev setup still works.moon run <tool>:compose-downremoves only this worktree's routes and project.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

