{"id":48805746,"url":"https://github.com/kenfdev/devcontainer-wt","last_synced_at":"2026-04-14T05:03:40.930Z","repository":{"id":342457070,"uuid":"1162093006","full_name":"kenfdev/devcontainer-wt","owner":"kenfdev","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-06T02:30:50.000Z","size":80,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-06T06:57:37.001Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kenfdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-19T21:33:00.000Z","updated_at":"2026-03-06T02:30:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kenfdev/devcontainer-wt","commit_stats":null,"previous_names":["kenfdev/devcontainer-wt"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kenfdev/devcontainer-wt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fdevcontainer-wt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fdevcontainer-wt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fdevcontainer-wt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fdevcontainer-wt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kenfdev","download_url":"https://codeload.github.com/kenfdev/devcontainer-wt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fdevcontainer-wt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31782743,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-14T05:03:07.134Z","updated_at":"2026-04-14T05:03:40.921Z","avatar_url":"https://github.com/kenfdev.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# devcontainer-wt\n\nSeamless devcontainer + git worktree workflows. Run multiple feature branches simultaneously, each in its own isolated devcontainer with its own database, routed via Traefik subdomains.\n\n## What You Get\n\n- **Git works inside containers** -- worktree `.git` file resolution is fixed automatically via volume mount (no file mutation).\n- **No port conflicts** -- Traefik routes by subdomain, so every worktree container can listen on the same internal port.\n- **Per-worktree database** -- each worktree gets its own database, created automatically on startup.\n- **Per-worktree env vars** -- `.env.app.template` is expanded per worktree with `${WORKTREE_NAME}`, `${BRANCH_NAME}`, `${PROJECT_NAME}`, etc.\n- **Worktree CLI** -- `./worktree.sh` manages the full worktree lifecycle (create, remove, list, prune) with cleanup hooks.\n\n## Install\n\nRun this from your project's root directory:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/kenfdev/devcontainer-wt/main/install.sh | bash\n```\n\n### Minimum mode\n\nFor projects that don't need Traefik routing or shared infrastructure (CLI tools, libraries, etc.), use `--minimum`:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/kenfdev/devcontainer-wt/main/install.sh | bash -s -- --minimum\n```\n\nMinimum mode gives you:\n- Git worktree fix (the primary reason to use devcontainer-wt)\n- Per-worktree containers with full isolation\n- Per-worktree env vars (via `.env.app.template`)\n- Worktree CLI (`./worktree.sh`) for lifecycle management\n- Orphan container detection\n\nWhat it skips:\n- No Traefik reverse proxy (access your app via VS Code port forwarding)\n- No shared infrastructure services (no Docker Compose profiles)\n- No custom Docker network (each worktree uses its own default network)\n\nYou can upgrade to full mode later by re-running the installer without `--minimum`.\n\n### Full mode (default)\n\nThe installer will:\n- Download the template files from GitHub\n- Set up `.devcontainer/` with all required configuration\n- Prompt to backup if `.devcontainer/` already exists\n- Optionally install AI skill files for agent-assisted customization\n\nAfter installing, see **[CUSTOMIZING.md](.agents/skills/devcontainer-wt/references/CUSTOMIZING.md)** for which files to edit and which to leave alone.\n\n## URL Pattern\n\nAll URLs follow this pattern:\n\n```\nhttp://{BRANCH_NAME}.{PROJECT_NAME}.localhost\n```\n\n- **`PROJECT_NAME`** = your main repo's directory name (e.g., if you cloned into `myapp/`, the project name is `myapp`).\n- **`BRANCH_NAME`** = the current git branch name, sanitized for DNS (slashes become hyphens, e.g., `feature/login` becomes `feature-login`).\n\nFor example, if you clone the repo into a directory called `myapp`:\n\n| What | URL |\n|---|---|\n| Main worktree app (branch `main`) | `http://main.myapp.localhost` |\n| Feature worktree (branch `feature-x`) | `http://feature-x.myapp.localhost` |\n| Traefik dashboard | `http://traefik.myapp.localhost` |\n\n\u003e **Tip:** After the container starts, check `.devcontainer/.env` to see the resolved values for `PROJECT_NAME` and `BRANCH_NAME`. These determine your URLs.\n\n## Prerequisites\n\n| Requirement | Notes |\n|---|---|\n| **Docker Desktop** (macOS) or **Docker Engine** (Linux) | Must be running before you start. |\n| **VS Code** + **Dev Containers extension** | Install [ms-vscode-remote.remote-containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). |\n| **git** | Any recent version with worktree support. |\n| **envsubst** | Pre-installed on most Linux. On macOS: `brew install gettext`. |\n\n## Directory Structure\n\n```\nmyapp/                              # \u003c-- you are here (main worktree)\n  .git/                             # git database (directory)\n  .devcontainer/\n    devcontainer.json               # devcontainer configuration\n    docker-compose.yml              # per-worktree app service\n    docker-compose.infra.yml        # shared infra (Traefik, Postgres) -- profiles-gated\n    Dockerfile                      # container image\n    init.sh                         # host-side init script\n    .env                            # generated (gitignored)\n    .env.app                        # generated (gitignored)\n  .env.app.template                 # per-worktree env var template (tracked)\n  worktree.sh                       # worktree lifecycle CLI\n\nmyapp-feature-x/                    # worktree (sibling directory)\n  .git                              # file pointing to ../myapp/.git/worktrees/feature-x\n  .devcontainer/                    # same files (tracked in git)\n  src/                              # same code, different branch\n```\n\n## Step 1: Clone and Open the Main Worktree\n\nThe main worktree **must be started first**. It runs the shared infrastructure (Traefik, Postgres).\n\n```bash\n# Clone the repo (the directory name becomes your PROJECT_NAME)\ngit clone \u003cyour-repo-url\u003e myapp\ncd myapp\n```\n\nOpen the folder in VS Code:\n\n```bash\ncode .\n```\n\nVS Code will detect `.devcontainer/devcontainer.json` and show a notification:\n\n\u003e **Folder contains a Dev Container configuration file.** Reopen folder to develop in a container.\n\nClick **\"Reopen in Container\"** (or run the command palette: `Dev Containers: Reopen in Container`).\n\n### What Happens Behind the Scenes\n\n1. **`init.sh` runs on the host** (via `initializeCommand`):\n   - Detects this is the main worktree (`.git` is a directory).\n   - Derives `PROJECT_NAME` from the directory name (e.g., `myapp`).\n   - Sets `WORKTREE_NAME` to the directory name (e.g., `myapp`).\n   - Sets `BRANCH_NAME` from the current git branch (e.g., `main`).\n   - Writes all resolved values to `.devcontainer/.env`.\n   - Sets `COMPOSE_PROFILES=infra` so Traefik (and any infrastructure services you've added) start.\n   - Creates Docker network `devnet-{PROJECT_NAME}`.\n   - Expands `.env.app.template` into `.devcontainer/.env.app`.\n\n2. **Docker Compose brings up containers**:\n   - `traefik-{PROJECT_NAME}` -- reverse proxy on port 80 (configurable).\n   - Any infrastructure services you've added (Postgres, Redis, etc.).\n   - `app-{PROJECT_NAME}-{WORKTREE_NAME}` -- your app container.\n\n3. **Git works immediately** -- the git common directory is mounted at the same absolute host path, so `.git` file references resolve directly inside the container.\n\n### Verify It Works\n\nFirst, check the generated values:\n\n```bash\ncat .devcontainer/.env\n# Look for PROJECT_NAME and BRANCH_NAME -- these determine your URLs.\n```\n\nThen open your browser. Assuming you cloned into `myapp/`:\n\n| URL | What It Shows |\n|---|---|\n| http://main.myapp.localhost | Your app (main worktree) |\n| http://traefik.myapp.localhost | Traefik dashboard (shows all routes) |\n\nYour app should be reachable if you've set up a dev server. The sample app shows project/worktree info.\n\n\u003e **Note (macOS):** `*.localhost` resolves to `127.0.0.1` out of the box. No `/etc/hosts` changes needed.\n\u003e\n\u003e **Note (Linux):** If subdomains don't resolve, see [Platform Notes: Linux](#linux-native-docker).\n\n## Step 2: Create a Feature Worktree\n\nWith the main worktree running, create a new worktree from the **host terminal** (not inside the container):\n\n```bash\n# From the main repo directory\ncd myapp\n\n# Create a worktree using the CLI\n./worktree.sh add feature-x\n```\n\nThis creates `myapp-feature-x/` next to `myapp/` with a `.git` **file** (not directory) pointing back to the main repo's git database.\n\nNow open it in VS Code:\n\n```bash\ncode ../myapp-feature-x\n```\n\n\u003e **Tip:** You can also run `./worktree.sh add` without arguments to be prompted for a branch name, or use the standard git command directly: `git worktree add ../myapp-feature-x -b feature-x`.\n\nClick **\"Reopen in Container\"** again.\n\n### What Happens This Time\n\n1. **`init.sh` runs on the host**:\n   - Detects this is a worktree (`.git` is a file, not a directory).\n   - Sets `PROJECT_NAME=myapp` (derived from the main repo, not the worktree directory).\n   - Sets `WORKTREE_NAME=myapp-feature-x` (from the worktree directory name).\n   - Sets `BRANCH_NAME=feature-x` (from the current git branch).\n   - Does **not** set `COMPOSE_PROFILES=infra` -- infrastructure services are NOT started again.\n   - Joins the existing `devnet-myapp` network.\n\n2. **Only the app container starts**: `app-myapp-myapp-feature-x`.\n\n3. **Git works immediately** -- the git common directory is mounted at the same absolute host path, so the `.git` file's host path references resolve directly. Git commands (`log`, `blame`, `status`, `commit`) work out of the box.\n\n### Verify the Feature Worktree\n\n| URL | What It Shows |\n|---|---|\n| http://feature-x.myapp.localhost | Your app (feature-x worktree) |\n\nCheck the Traefik dashboard at `http://traefik.myapp.localhost` -- you should see routes for both worktrees.\n\n### Verify Git Works Inside the Container\n\nOpen a terminal inside the feature worktree's VS Code window and run:\n\n```bash\ngit status\ngit log --oneline -5\ngit branch\n```\n\nAll commands should work normally, even though this is a worktree inside a container.\n\n## Step 3: Work on Multiple Worktrees Simultaneously\n\nRepeat Step 2 for as many branches as you need:\n\n```bash\n# Another feature\n./worktree.sh add feature-y\ncode ../myapp-feature-y\n\n# PR review\ngit fetch origin\ngit worktree add ../myapp-pr-42 origin/some-pr-branch\ncode ../myapp-pr-42\n```\n\nEach one gets:\n- Its own VS Code window and devcontainer.\n- Its own Traefik route: `http://feature-y.myapp.localhost`, `http://some-pr-branch.myapp.localhost`.\n- Its own database (if you've configured one).\n- Full git support inside the container.\n\n## Step 4: Clean Up a Worktree\n\nUse the CLI to remove a worktree and its container in one step:\n\n```bash\n./worktree.sh remove ../myapp-feature-x\n```\n\nThis will:\n1. Run the cleanup hook (`.devcontainer/hooks/on-remove.sh`) for project-specific cleanup (e.g., dropping databases).\n2. Stop and remove the worktree's container.\n3. Remove the worktree directory (refuses if there are uncommitted changes -- use `git worktree remove --force` manually to override).\n4. Prune any other orphaned containers.\n\nTo clean up orphaned containers without removing a specific worktree:\n\n```bash\n./worktree.sh prune\n```\n\nTo see all worktrees and their container status:\n\n```bash\n./worktree.sh list\n```\n\n\u003e **Tip:** Customize `.devcontainer/hooks/on-remove.sh` to add project-specific cleanup (dropping databases, clearing caches). See [CUSTOMIZING.md](.agents/skills/devcontainer-wt/references/CUSTOMIZING.md).\n\n## Customization\n\n### Change the Traefik Port\n\nIf port 80 is in use, set `TRAEFIK_PORT` before opening the main worktree:\n\n```bash\nexport TRAEFIK_PORT=8000\ncode myapp\n```\n\nThen access your app at `http://{BRANCH_NAME}.{PROJECT_NAME}.localhost:8000`.\n\n### Change the Postgres Host Port\n\n```bash\nexport POSTGRES_HOST_PORT=25432\n```\n\nThe default is `15432` to avoid conflicts with a host Postgres on `5432`.\n\n### Override the Project Name\n\nBy default, the project name is derived from the main repo's directory name. Override it:\n\n```bash\nexport PROJECT_NAME=my-custom-name\n```\n\nThis changes all routes (`*.my-custom-name.localhost`), container names, database names, and the Docker network name.\n\n### Add Environment Variables\n\nEdit `.env.app.template` (tracked in git) to add per-worktree variables:\n\n```bash\nDATABASE_URL=postgres://dev:dev@postgres-${PROJECT_NAME}:5432/${PROJECT_NAME}_${WORKTREE_NAME}\nREDIS_URL=redis://redis-${PROJECT_NAME}:6379/0\nAPP_NAME=${PROJECT_NAME}-${WORKTREE_NAME}\nMY_SECRET=${MY_SECRET}  # reads from host env var\n```\n\nEach worktree's `init.sh` expands this into `.devcontainer/.env.app` (gitignored).\n\n### Add Infrastructure Services\n\nEdit `.devcontainer/docker-compose.infra.yml` to add services under the `infra` profile. For example, to add Redis:\n\n```yaml\n  redis:\n    profiles: [infra]\n    image: redis:7-alpine\n    container_name: \"redis-${PROJECT_NAME}\"\n    networks:\n      - devnet\n    restart: unless-stopped\n```\n\n### Change the App Port\n\nIf your app listens on a port other than 3000, update the Traefik label in `.devcontainer/docker-compose.yml`:\n\n```yaml\n- \"traefik.http.services.${PROJECT_NAME}-${WORKTREE_NAME}.loadbalancer.server.port=4000\"\n```\n\n### Headless Usage (devcontainer CLI / AI Agents)\n\nThe template works without VS Code. All lifecycle hooks run the same way:\n\n```bash\n# Start a worktree container\ndevcontainer up --workspace-folder ../myapp-feature-x\n\n# Run commands inside\ndevcontainer exec --workspace-folder ../myapp-feature-x bash\n```\n\nFor git authentication, set `GITHUB_TOKEN` on the host:\n\n```bash\nexport GITHUB_TOKEN=ghp_xxx\ndevcontainer up --workspace-folder ../myapp-feature-x\n```\n\n## How It Works\n\n### The Git Worktree Problem\n\nA git worktree's `.git` is a **file** containing an absolute host path:\n\n```\ngitdir: /Users/you/myapp/.git/worktrees/feature-x\n```\n\nWhen mounted into a container at `/workspaces/myapp-feature-x`, this host path doesn't exist. All git commands fail.\n\n### The Volume Mount Fix\n\nInstead of rewriting the `.git` file (which would mutate the bind-mounted file and break host-side git), `docker-compose.yml` mounts the git common directory at the same absolute host path inside the container:\n\n```\nHost: /Users/you/myapp/.git/  →  Container: /Users/you/myapp/.git/  (same path)\n```\n\nWhen git reads the `.git` file and follows `/Users/you/myapp/.git/worktrees/feature-x`, the path exists inside the container because the volume mount places the git directory at the exact same path.\n\nThe `.git` file is **never modified**. No symlink or post-start script needed. The host is unaffected.\n\n### Infrastructure Isolation\n\n- **Main worktree** starts with `COMPOSE_PROFILES=infra`, which activates Traefik and Postgres.\n- **Feature worktrees** do not set this profile, so they only start the app container.\n- All containers join the same Docker network (`devnet-{PROJECT_NAME}`), so they can reach each other by container name.\n- Traefik auto-discovers app containers via Docker labels and routes traffic by subdomain.\n\n## Architecture\n\n```\n  Browser\n    |\n    v\n  Traefik (port 80)\n    |\n    |-- {BRANCH}.{PROJECT}.localhost    --\u003e app-{PROJECT}-{WORKTREE}:3000\n    |-- traefik.{PROJECT}.localhost    --\u003e Traefik dashboard\n    |\n  Docker Network: devnet-{PROJECT}\n    |\n    |-- postgres-{PROJECT}:5432\n    |     |-- DB: {PROJECT}_{WORKTREE_1}\n    |     |-- DB: {PROJECT}_{WORKTREE_2}\n    |\n    |-- app-{PROJECT}-{WORKTREE_1}   (main worktree container)\n    |-- app-{PROJECT}-{WORKTREE_2}   (feature worktree container)\n```\n\n## Platform Notes\n\n### macOS (Docker Desktop)\n\n- `*.localhost` resolves to `127.0.0.1` by default. No configuration needed.\n- Chrome works out of the box. Firefox may require `about:config` -\u003e set `network.dns.localDomains` to include your subdomains, or use `/etc/hosts`.\n\n### Linux (Native Docker)\n\n`*.localhost` wildcard resolution may not work. Two options:\n\n**Option A: `/etc/hosts` (manual, per worktree)**\n\n```\n127.0.0.1 main.myapp.localhost\n127.0.0.1 feature-x.myapp.localhost\n127.0.0.1 traefik.myapp.localhost\n```\n\n**Option B: `dnsmasq` (automatic wildcard)**\n\n```\n# /etc/dnsmasq.d/localhost.conf\naddress=/localhost/127.0.0.1\n```\n\n## Troubleshooting\n\n### \"Reopen in Container\" does nothing or fails immediately\n\nCheck that Docker Desktop is running:\n```bash\ndocker ps\n```\n\n### App not reachable at `*.localhost`\n\n1. Check your actual URLs: `cat .devcontainer/.env` to see `PROJECT_NAME` and `BRANCH_NAME`.\n2. Check Traefik is running: `docker ps | grep traefik`\n3. Check the Traefik dashboard: `http://traefik.{PROJECT_NAME}.localhost`\n4. Test with curl: `curl -H \"Host: {BRANCH}.{PROJECT}.localhost\" http://localhost/`\n5. On Linux, check DNS resolution (see [Platform Notes](#linux-native-docker)).\n\n### Git commands fail inside a worktree container\n\nCheck if the git common directory is mounted at the correct path:\n```bash\n# Inside the container\nls -la /Users/  # should see your host path structure\n```\n\nCheck that the git common directory volume mount is correct in `.devcontainer/.env`.\n\n### Database connection refused\n\nThe main worktree must be running (it hosts Postgres). Check:\n```bash\ndocker ps | grep postgres\n```\n\n### Port 80 already in use\n\nSet a custom Traefik port before starting:\n```bash\nexport TRAEFIK_PORT=8000\n```\n\nThen access apps at `http://{BRANCH}.{PROJECT}.localhost:8000`.\n\n### `envsubst: command not found` (macOS)\n\n```bash\nbrew install gettext\n```\n\n`envsubst` is included in the `gettext` package.\n\n## Limitations\n\n- **Main worktree must start first.** Infrastructure services (Traefik, Postgres) only run from the main worktree.\n- **Docker Compose only.** The template requires Docker Compose as the devcontainer backend.\n- **Name collisions.** Branch names like `feature/login` and `feature-login` both sanitize to `feature-login`. Use distinct branch names.\n- **GitHub Codespaces not supported.** Different constraints (no Traefik, no sibling worktrees).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkenfdev%2Fdevcontainer-wt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkenfdev%2Fdevcontainer-wt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkenfdev%2Fdevcontainer-wt/lists"}