{"id":48805738,"url":"https://github.com/kenfdev/container-wt","last_synced_at":"2026-04-14T05:03:38.939Z","repository":{"id":344597901,"uuid":"1182315654","full_name":"kenfdev/container-wt","owner":"kenfdev","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-29T12:40:32.000Z","size":49,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T15:53:20.618Z","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-03-15T10:56:47.000Z","updated_at":"2026-03-29T12:40:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kenfdev/container-wt","commit_stats":null,"previous_names":["kenfdev/container-wt"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kenfdev/container-wt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fcontainer-wt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fcontainer-wt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fcontainer-wt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fcontainer-wt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kenfdev","download_url":"https://codeload.github.com/kenfdev/container-wt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kenfdev%2Fcontainer-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:02.900Z","updated_at":"2026-04-14T05:03:38.934Z","avatar_url":"https://github.com/kenfdev.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# container-wt\n\nSeamless Docker + git worktree workflows. Run multiple feature branches simultaneously, each in its own isolated container with its own database, routed via Traefik subdomains. No devcontainer CLI or VS Code required.\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** -- `.worktree/.env.app.template` is expanded per worktree with `${WORKTREE_NAME}`, `${BRANCH_NAME}`, `${PROJECT_NAME}`, etc.\n- **Personal Dockerfile** -- each developer can customize their container image without touching shared files.\n- **Clean project root** -- all container files live inside `.worktree/`, only infra compose and env template at root.\n\n## Install\n\nRun this from your project's root directory:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/kenfdev/container-wt/main/install.sh | bash\n```\n\nThe installer will:\n- Download the template files from GitHub\n- Set up `.worktree/` with Dockerfiles, compose files, and init script\n- Run `init.sh` to generate `.env` files\n- Prompt to backup if existing files are detected\n\n### Slim mode\n\nFor projects that don't need shared infrastructure (Traefik, Docker network, Postgres, Redis, etc.), use `--slim` to install a standalone app container setup:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/kenfdev/container-wt/main/install.sh | bash -s -- --slim\n```\n\nSlim mode skips:\n- Root `docker-compose.yml` (shared infrastructure)\n- Root `.env` (infrastructure variables)\n- Docker network creation\n- Traefik labels and routing\n\nThe app exposes ports directly (default `3000:3000`) instead of routing through Traefik.\n\n## Directory Structure\n\n```\nmyapp/                                    # \u003c-- you are here (main worktree)\n  .git/                                   # git database (directory)\n  .worktree/                          # container-wt files\n    Dockerfile.base                       # team-shared base image\n    Dockerfile.app                        # default app image (FROM base)\n    docker-compose.yml                    # per-worktree app service\n    docker-compose.local.yml              # personal overrides (gitignored)\n    docker-compose.local.example.yml      # template for personal overrides\n    init.sh                               # host-side: generates .env, .env.app\n    Dockerfile.local.example              # example personal Dockerfile\n    .env                                  # generated, compose vars (gitignored)\n    .env.app                              # generated, container env (gitignored)\n    .env.app.template                     # per-worktree env var template (tracked)\n  docker-compose.yml                      # shared infra (Traefik, Postgres, Redis)\n  .env                                    # generated, minimal (gitignored)\n  .worktreeinclude                        # glob patterns for worktree file copy\n  .worktree/hooks/\n    on-create.sh                          # worktree creation hook\n    on-delete.sh                          # worktree deletion hook\n```\n\n## Quick Start\n\n### 1. Start Infrastructure\n\n```bash\ncd myapp\ndocker compose up -d\n# Traefik dashboard: http://traefik.myapp.localhost\n```\n\n### 2. Start the App Container\n\n```bash\ncd .worktree\ndocker compose up -d --build\n# App: http://main.myapp.localhost\n```\n\n### 3. Enter the Container\n\n```bash\ncd .worktree\ndocker compose exec app zsh\n```\n\n### 4. Create a Feature Worktree\n\nFrom the host terminal:\n\n```bash\ngit worktree add ../feature-x -b feature-x\ncd ../feature-x\n.worktree/hooks/on-create.sh   # copies gitignored files + runs init.sh\ncd .worktree \u0026\u0026 docker compose up -d --build\n# App: http://feature-x.myapp.localhost\n```\n\nWith [git-wt](https://github.com/k1LoW/git-wt) (hooks run automatically):\n\n```bash\ngit wt feature-x\ncd ../feature-x/.worktree\ndocker compose up -d --build\n```\n\n## URL Pattern\n\n```\nhttp://{BRANCH_NAME}.{PROJECT_NAME}.localhost\n```\n\n| What | URL |\n|---|---|\n| Main worktree (branch `main`) | `http://main.myapp.localhost` |\n| Feature worktree (`feature-x`) | `http://feature-x.myapp.localhost` |\n| Traefik dashboard | `http://traefik.myapp.localhost` |\n\n## Docker Compose Commands\n\nRun infra commands from the **project root** and app commands from the **`.worktree/`** directory:\n\n| Directory | Command | What It Does |\n|---|---|---|\n| project root | `docker compose up -d` | Start shared infrastructure |\n| `.worktree/` | `docker compose up -d --build` | Start app container |\n| `.worktree/` | `docker compose down` | Stop the app container |\n| `.worktree/` | `docker compose exec app zsh` | Open a shell in the running app container |\n| `.worktree/` | `docker compose build` | Rebuild the app image |\n| `.worktree/` | `docker compose logs -f app` | Tail app container logs |\n\n## Dockerfile Layering\n\n```\n.worktree/Dockerfile.base     Team-shared base (ubuntu + git, curl, zsh)\n      |\n.worktree/Dockerfile.app      Default app (project-specific deps)\n      |\n.worktree/Dockerfile.local       Personal (neovim, claude, etc.)\n```\n\nAll Dockerfiles use `ARG BASE_IMAGE` / `FROM ${BASE_IMAGE}`. The base image name is passed as a build arg by the compose file, and `depends_on` ensures the base image is always built first.\n\n### Personal Dockerfile Setup\n\n1. Copy `.worktree/Dockerfile.local.example` to `.worktree/Dockerfile.local`\n2. Copy `.worktree/docker-compose.local.example.yml` to `.worktree/docker-compose.local.yml`\n3. Uncomment the build override in `docker-compose.local.yml`\n4. Add your Dockerfile to `.worktreeinclude.local` so it gets copied to new worktrees:\n   ```\n   .worktree/Dockerfile.local\n   ```\n\n## Customization\n\n### Add Infrastructure Services\n\nEdit `docker-compose.yml` to add services (Postgres, Redis, etc.):\n\n```yaml\n  postgres:\n    image: postgres:16\n    container_name: \"postgres-${PROJECT_NAME:-myapp}\"\n    environment:\n      POSTGRES_PASSWORD: dev\n      POSTGRES_USER: dev\n    ports:\n      - \"${POSTGRES_HOST_PORT:-15432}:5432\"\n    volumes:\n      - pgdata:/var/lib/postgresql/data\n    networks:\n      - devnet\n    restart: unless-stopped\n```\n\n### Add Environment Variables\n\nEdit `.worktree/.env.app.template` (tracked in git):\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}\n```\n\n### Change the App Port\n\nUpdate the Traefik label in `.worktree/docker-compose.yml`:\n\n```yaml\n- \"traefik.http.services.${PROJECT_NAME}-${WORKTREE_NAME}.loadbalancer.server.port=4000\"\n```\n\n### Change the Traefik Port\n\n```bash\nTRAEFIK_PORT=8000 docker compose up -d\n```\n\n## Prerequisites\n\n| Requirement | Notes |\n|---|---|\n| **Docker Desktop** (macOS) or **Docker Engine** (Linux) | Must be running. |\n| **git** | Any recent version with worktree support. |\n| **envsubst** | Pre-installed on most Linux. On macOS: `brew install gettext`. The installer checks for this. |\n\n## How the Git Fix Works\n\nA git worktree's `.git` is a **file** containing an absolute host path. When mounted into a container, this path doesn't exist. The template 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\nThe `.git` file is **never modified**. No symlink or post-start script needed.\n\n## Platform Notes\n\n### macOS\n\n`*.localhost` resolves to `127.0.0.1` by default. No configuration needed.\n\n### Linux\n\n`*.localhost` wildcard resolution may not work. Use `/etc/hosts` or `dnsmasq`:\n\n```\n# /etc/hosts\n127.0.0.1 main.myapp.localhost feature-x.myapp.localhost traefik.myapp.localhost\n\n# Or dnsmasq for wildcard\naddress=/localhost/127.0.0.1\n```\n\n## Limitations\n\n- **Main worktree must start infra first.** Infrastructure services only run from the main worktree.\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%2Fcontainer-wt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkenfdev%2Fcontainer-wt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkenfdev%2Fcontainer-wt/lists"}