ahab guessed that the django service is called django and the postgres one db, falling back on AHAB_DJANGO_CONTAINER and AHAB_POSTGRES_CONTAINER. The standard project layout happens to agree, but nothing enforces it, and a stack naming them web and database could not use ahab without setting both variables. One `docker compose config --format json` call, roughly 120ms, resolves the stack even while it is down, and the services are identified from what they are rather than what they are called: - postgres is the service whose image is a postgres flavour, matching postg, timescale, pgvector or citus. Nothing else counts. - django is the service that both builds an image and has DJANGO_SETTINGS_MODULE in its environment. A celery worker sharing the same build and env_file matches too, so published ports break the tie: the service answering requests wins. Neither guess is allowed to be wrong quietly. No match, or two candidates that cannot be told apart, is an error naming the services it looked at. There is no variable to fall back on: both container variables are gone, along with the guessed defaults they backed up, so an ambiguous stack is fixed in the compose file rather than worked around per developer. Every service lookup goes through these rules, so `compose exec` and the whole django group agree on which container they mean. POSTGRES_USER and POSTGRES_DB come off the detected service, so dropdb, createdb, pg_restore and pg_dump stop assuming the role and database are both literally `db`, falling back to that only when the service declares neither. Note that env_file entries are merged into a service's environment, so POSTGRES_* is not safe for identifying the database service: one such line in a project's .env would make the django service match as well. That is why identification uses the image and only credentials use the environment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
122 lines
3.7 KiB
Markdown
122 lines
3.7 KiB
Markdown
# ahab
|
|
|
|
A wrapper around `docker compose` for our dockerized django projects, so the
|
|
same commands work in every repository.
|
|
|
|
## installing
|
|
|
|
You will need rust installed. Clone repo and run:
|
|
```bash
|
|
cargo install --path .
|
|
```
|
|
|
|
To print the underlying docker commands as they run, build with debug
|
|
assertions:
|
|
```bash
|
|
cargo install --path . --debug
|
|
```
|
|
|
|
## shell completion
|
|
|
|
`ahab completions <shell>` writes a completion script to stdout, for bash,
|
|
elvish, fish, powershell or zsh:
|
|
|
|
```bash
|
|
ahab completions zsh > ~/.local/share/zsh/completions/_ahab
|
|
eval "$(ahab completions zsh)" # or one line in .zshrc, never goes stale
|
|
```
|
|
|
|
Completions are also generated during the build. They land in
|
|
`target/*/build/*/out/` by default, and `SHELL_COMPLETIONS_DIR_<SHELL>`
|
|
installs a single shell's file straight into place:
|
|
|
|
```bash
|
|
SHELL_COMPLETIONS_DIR_ZSH=~/.local/share/zsh/completions \
|
|
SHELL_COMPLETIONS_DIR_FISH=~/.config/fish/completions \
|
|
cargo install --path .
|
|
```
|
|
|
|
`SHELL_COMPLETIONS_DIR` writes every shell into one directory instead.
|
|
|
|
## the compose file
|
|
|
|
`ahab` does not pass `-f`. docker compose finds the file itself, so set
|
|
docker's own `COMPOSE_FILE` when it is not in the working directory, including
|
|
its `base.yaml:override.yaml` form. A project's `.env` is a good place for it,
|
|
since docker reads that too:
|
|
|
|
```
|
|
COMPOSE_FILE=docker/docker-compose.yaml
|
|
```
|
|
|
|
## compose
|
|
|
|
Wrappers around the matching `docker compose` call, plus `exec` and `bash`
|
|
which default to the django service.
|
|
|
|
```bash
|
|
ahab compose up # also build, down, ps, start, stop
|
|
ahab compose rebuild # stop, build, up
|
|
ahab compose restart # stop, up, so containers are recreated
|
|
ahab compose bash # shell in the django service
|
|
ahab compose exec <cmd>
|
|
```
|
|
|
|
## service detection
|
|
|
|
`ahab` finds the services it needs in `docker compose config`, so they can be
|
|
named anything:
|
|
|
|
- **postgres**: the service whose image is a postgres flavour, matching
|
|
`postg`, `timescale`, `pgvector` or `citus`
|
|
- **django**: the service that both builds an image and sets
|
|
`DJANGO_SETTINGS_MODULE`. A worker sharing the same build and `env_file`
|
|
matches too, so the one publishing ports wins
|
|
|
|
If nothing matches, or two candidates cannot be told apart, `ahab` says so and
|
|
lists the services it looked at rather than guessing.
|
|
|
|
`POSTGRES_USER` and `POSTGRES_DB` are read off the detected postgres service, so
|
|
`dropdb`, `createdb`, `pg_restore` and `pg_dump` use the role and database the
|
|
project declares, falling back to `db` when it declares neither.
|
|
|
|
## django
|
|
|
|
```bash
|
|
ahab django manage <args> # manage.py in a fresh container
|
|
ahab django makemigrations
|
|
ahab django migrate <args>
|
|
ahab django shell
|
|
ahab django test
|
|
ahab django make-command <app> <name>
|
|
```
|
|
|
|
## postgres
|
|
|
|
```bash
|
|
ahab postgres dump <path> # pg_dump, custom format
|
|
ahab postgres import <path> # drop, create, pg_restore
|
|
```
|
|
|
|
## link
|
|
|
|
`ahab link` moves untracked paths out of the repository into an out-of-repo
|
|
store and symlinks them back, so a sandbox that mounts the repository sees a
|
|
dangling symlink instead of the contents, while the host resolves it as before.
|
|
|
|
```bash
|
|
ahab link add .env secrets/ # move out, leave symlinks behind
|
|
ahab link check # what a sandbox can still read
|
|
ahab link check --porcelain # `<code> <path>`, for scripts
|
|
```
|
|
|
|
The store lives under
|
|
`${XDG_DATA_HOME:-$HOME/.local/share}/ahab/<host>/<owner>/<repo>/`, derived
|
|
from the git `origin` remote.
|
|
|
|
## configuration
|
|
|
|
Currently `ahab` respects the following environment variables.
|
|
|
|
- `AHAB_LINK_ROOT`: root of the out-of-repo store `ahab link` moves paths into - defaults to `${XDG_DATA_HOME:-$HOME/.local/share}/ahab`
|