diff --git a/.config/wt.toml b/.config/wt.toml new file mode 100644 index 0000000..cd9b9d1 --- /dev/null +++ b/.config/wt.toml @@ -0,0 +1,54 @@ +# Worktrunk project configuration (https://worktrunk.dev). +# +# Gives every worktree its own Docker Compose environment so several branches +# can run `task run` / `task tests` side by side. docker-compose.yml and the +# Taskfile read the `.env` written below and fall back to the historical +# defaults (port 8000, image python.ie/website-dev) when it is absent, so +# nothing changes for contributors who do not use worktrunk. +# +# The naming templates in pre-start and post-remove must stay in sync. + +# Blocking, so the `.env` exists before anyone runs docker compose in the new +# worktree. It pins, per branch: +# - COMPOSE_PROJECT_NAME: namespaces containers, networks and volumes +# (including the Postgres data volume), so each worktree has its own database; +# - DOCKER_IMAGE: a per-branch tag, so rebuilding the image on a branch that +# changes dependencies does not break the other worktrees; +# - WEB_PORT / PGPORT: deterministic host ports (hash_port), so published ports +# never collide. PGPORT is also what psql / `task database:pull` use on the host. +[pre-start] +env = """ +cat > .env </dev/null || true +""" + +# Shows each worktree's dev server URL in `wt list` (matches WEB_PORT above). +[list] +url = "http://localhost:{{ branch | hash_port }}" diff --git a/.gitignore b/.gitignore index d96d0d2..b954306 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,8 @@ project.db .envrc development.env production.env +# Per-worktree Docker Compose overrides (generated, see .config/wt.toml) +.env # Database dumps and local databases *.dump diff --git a/CLAUDE.md b/CLAUDE.md index 7d13f9c..ea88654 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,7 @@ uv run python pythonie/manage.py runserver --settings=pythonie.settings.dev ### Docker Development (preferred) -Uses Task for most operations. Requires docker-compose with services: web, postgres, redis, minio. +Uses Task for most operations. Requires docker-compose with services: web, postgres, redis. With worktrunk (`wt`), `.config/wt.toml` writes a per-worktree `.env` (compose project name, image tag, ports) and cleans up the worktree's containers and volumes on `wt remove`; see DEVELOPMENT.md. ```bash # Build docker image diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 73007fd..d199207 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -147,6 +147,42 @@ uv run python pythonie/manage.py createsuperuser --settings=pythonie.settings.de uv run python pythonie/manage.py runserver --settings=pythonie.settings.dev ``` +### Parallel Worktrees with worktrunk (optional) + +[worktrunk](https://worktrunk.dev) (`wt`) manages one git worktree per branch. The project hooks in `.config/wt.toml` give every worktree its own Docker Compose environment, so several branches can run `task run` or `task tests` at the same time without sharing a database, an image or a port. + +```bash +# Create a worktree for a new branch (runs the hooks below) +wt switch --create my-feature + +# Inside it, the usual commands work unchanged +task docker:build +task django:migrate +task run + +# Show every worktree with its dev server URL +wt list + +# Remove the worktree AND its Docker resources (see below) +wt remove my-feature +``` + +The first time, worktrunk asks you to approve the project hooks. Review them and approve with `wt config approvals add`. + +What the hooks do: + +| Hook | What it does | +|------|--------------| +| `pre-start` | Writes a `.env` in the new worktree with `COMPOSE_PROJECT_NAME`, `DOCKER_IMAGE`, `WEB_PORT` and `PGPORT`, all derived from the branch name | +| `post-start` | Runs `uv sync --all-groups` in the background to create the worktree's `.venv` | +| `post-remove` | Runs `docker compose -p down --volumes --remove-orphans` and deletes the branch image | + +Docker Compose reads `.env` automatically, so each worktree gets its own containers, network and volumes (including the PostgreSQL data), its own image tag (`python.ie/website-dev:`) and deterministic host ports. Check them with `cat .env`; the web server is at `http://localhost:$WEB_PORT/`. The Taskfile also loads `.env`, so host tools such as `psql` or `task database:pull` talk to the worktree's PostgreSQL on `PGPORT`. + +**Removing a worktree deletes its database.** `wt remove` (and `wt merge`, which removes the worktree) destroys the Docker volumes of that worktree's compose project. Other worktrees and the main checkout use different project names and are not affected. Dump anything you want to keep first. + +Without worktrunk, or in a worktree created before these hooks existed, there is no `.env` and the defaults apply: port 8000 for the web server, 5432 for PostgreSQL, image `python.ie/website-dev`, and a compose project named after the directory. To add the per-worktree `.env` to an existing worktree, run `wt hook pre-start` inside it. + ### Environment Variables Create `development.env` at the root (copy from `development.env.example` if it exists): diff --git a/README.md b/README.md index ba51e72..e422982 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 6 2. Start supporting services: ```bash - docker compose up -d postgres redis minio + docker compose up -d postgres redis ``` 3. Run database migrations: @@ -48,6 +48,8 @@ Website for Python Ireland (python.ie / pycon.ie) community, built with Django 6 7. Visit http://127.0.0.1:8000/ to see the site with sample content 8. Access Wagtail admin at http://127.0.0.1:8000/admin/ +Working on several branches at once? See [Parallel Worktrees with worktrunk](DEVELOPMENT.md#parallel-worktrees-with-worktrunk-optional) to give each worktree its own database, image and ports. + ## Local Setup (Without Docker) If you prefer to develop without Docker: diff --git a/Taskfile.yaml b/Taskfile.yaml index 9781d74..2d90767 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -4,14 +4,16 @@ version: '3' env: - DOCKER_IMAGE: python.ie/website-dev PGDATABASE: pythonie PGPASSWORD: pythonie PGUSER: postgres PGHOST: 127.0.0.1 HEROKU_APP: pythonie -dotenv: ['production.env'] +# `.env` holds per-worktree overrides (COMPOSE_PROJECT_NAME, DOCKER_IMAGE, +# WEB_PORT, PGPORT) written by worktrunk, see .config/wt.toml. Missing files +# are ignored, so everything falls back to the defaults in docker-compose.yml. +dotenv: ['.env', 'production.env'] tasks: database:drop: @@ -193,12 +195,12 @@ tasks: docker:build: desc: Build the docker image cmds: - - docker build --no-cache -t $DOCKER_IMAGE . + - docker compose build --no-cache web docker:build:cached: desc: Build the docker image reusing the build cache (faster rebuilds) cmds: - - docker build -t $DOCKER_IMAGE . + - docker compose build web docker:run: desc: Run a shell into the docker container @@ -208,7 +210,7 @@ tasks: stack:pull: desc: Pull the docker images for the stack cmds: - - docker compose pull postgres minio mc redis + - docker compose pull postgres redis stack:services: desc: List the services from the docker compose file diff --git a/docker-compose.yml b/docker-compose.yml index 702d522..676973c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,15 @@ services: web: - image: python.ie/website-dev + build: . + # Image, host ports and the compose project name can be overridden from a + # `.env` file next to this one (worktrunk writes it for every worktree, see + # .config/wt.toml). The defaults below apply when there is no `.env`. + image: ${DOCKER_IMAGE:-python.ie/website-dev} ports: - - "8000:8000" + - "${WEB_PORT:-8000}:8000" command: /usr/bin/fish depends_on: - postgres - - minio - redis env_file: - development.env @@ -23,46 +26,18 @@ services: postgres: image: postgres:17 ports: - - "5432:5432" + - "${PGPORT:-5432}:5432" environment: POSTGRES_PASSWORD: pythonie redis: image: redis:6.2 - minio: - image: quay.io/minio/minio - ports: - - "9000:9000" - - "9001:9001" - command: server --console-address ":9001" /data - environment: - MINIO_ROOT_USER: pythonie - MINIO_ROOT_PASSWORD: pythonie - volumes: - - minio-data:/data - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] - interval: 30s - timeout: 20s - retries: 3 - - mc: - image: quay.io/minio/mc - links: - - minio - volumes: - - ./mc:/root/.mc - - ../s3:/s3 - test: - image: python.ie/website-dev + image: ${DOCKER_IMAGE:-python.ie/website-dev} environment: DJANGO_SETTINGS_MODULE: pythonie.settings.tests volumes: - .:/app working_dir: /app command: python pythonie/manage.py test pythonie --verbosity=3 - -volumes: - minio-data: