Metadata-Version: 2.4
Name: compose-outdated
Version: 0.4.1
Summary: Check your Docker Compose stack for image updates without pulling
Requires-Python: >=3.12
Requires-Dist: docker>=7.1.0
Requires-Dist: httpx>=0.28
Requires-Dist: packaging>=24
Requires-Dist: pyyaml>=6
Requires-Dist: rich>=13
Requires-Dist: typer>=0.15
Description-Content-Type: text/markdown

# compose-outdated

Check your Docker Compose stack for image updates without pulling any images. Shows current vs latest versions and fetches changelogs from GitHub.

## Features

- Parses `docker-compose.yml` / `compose.yaml` with full `include:` recursion
- Queries Docker Hub, GHCR, and lscr.io for available tags
- Compares versions within the same tag channel (e.g. `-alpine` tags stay in their lane)
- Fetches release notes from GitHub for each available update
- Supports environment variable interpolation (`${VAR:-default}`)
- Concurrent registry checks for fast results
- No images are pulled -- registry APIs only

## Setup

Requires Python 3.12+ and [uv](https://docs.astral.sh/uv/).

```sh
# Clone and install
git clone <repo-url> && cd compose-outdated
uv sync

# Or install directly with uv/pipx
uv tool install .
```

This installs two equivalent commands: `compose-outdated` and `cod`.

## Usage

```sh
# Check compose file in the current directory
cod

# Check a specific directory or file
cod /path/to/my-stack
cod /path/to/docker-compose.yml

# Skip changelog fetching (faster, table-only output)
cod --no-changelog

# Only check specific services
cod -s traefik -s redis

# Set a GitHub token for higher API rate limits (also reads GITHUB_TOKEN env var)
cod --github-token ghp_xxxxx

# Control concurrent registry requests (default: 5)
cod -n 10

# Show more changelog context per release (default: 8 lines, 0 = unlimited)
cod -l 20
cod -l 0

# Save the full report to a file (always untruncated)
cod -o report.txt

# Compact terminal output but full detail in the file
cod -l 3 -o report.txt
```

## Options

| Flag | Short | Description |
|---|---|---|
| `--changelog` / `--no-changelog` | `-c` / `-C` | Enable/disable GitHub changelog fetching (default: on) |
| `--github-token TEXT` | | GitHub API token for higher rate limits |
| `--service TEXT` | `-s` | Only check named service(s), can be repeated |
| `--concurrency INT` | `-n` | Max concurrent registry checks (default: 5) |
| `--body-lines INT` | `-l` | Max lines per changelog body in terminal (0 = unlimited, default: 8) |
| `--output PATH` | `-o` | Write full report to file (changelogs are never truncated in the file) |

## Exit codes

| Code | Meaning |
|---|---|
| `0` | All images are up to date |
| `1` | Error (compose file not found, parse failure, etc.) |
| `2` | Updates are available |

This makes it easy to use in scripts or CI:

```sh
cod --no-changelog /path/to/stack || echo "Updates available!"
```

## Supported registries

- **Docker Hub** (`docker.io`) -- uses the Hub REST API, no auth required for public images
- **GitHub Container Registry** (`ghcr.io`) -- uses OCI Distribution API with anonymous tokens
- **LinuxServer** (`lscr.io`) -- proxied through GHCR
- Any OCI-compliant registry should work via the Distribution spec fallback

## How changelogs are resolved

The tool tries several strategies to find the GitHub source repo for each image:

1. Built-in known mappings for popular images (traefik, postgres, redis, cloudflared, etc.)
2. GHCR / lscr.io path inference (image path usually matches the GitHub repo)
3. `docker-library/official-images` manifest lookup (for official `library/` images)
4. Docker Hub repository metadata (parses README for GitHub links)
5. GitHub search as a last resort

Once a repo is found, GitHub Releases are fetched and filtered to versions between your current tag and the latest.

## Compose file support

The tool handles real-world compose setups:

- `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`
- Recursive `include:` directives (nested includes are followed)
- Environment variable interpolation: `$VAR`, `${VAR}`, `${VAR:-default}`
- Digest-pinned images (`image:tag@sha256:...`) -- the tag is extracted for comparison
- Images without explicit tags default to `latest`

## Version comparison

Tags are compared within "channels" to avoid false positives:

- `1.25.3-alpine` is only compared against other `*-alpine` tags
- `v`-prefixed and non-prefixed duplicates are deduplicated (`v3.6.10` and `3.6.10` count as one)
- Non-semver tags like `latest`, `stable`, or commit SHAs are skipped

## Development

```sh
uv sync                                    # install deps
uv run pytest                              # run tests
uv run pytest --cov=compose_outdated       # run tests with coverage
uv run ruff check .                        # lint
uv run ruff format .                       # format
```

This project uses [Conventional Commits](https://www.conventionalcommits.org/). Prefix your commit messages with `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `ci:`, `refactor:`, etc.

## Releasing a new version

1. Ensure you're on `master` with a clean working tree and CI passing.
2. Run the version bump:
   ```sh
   uv run cz bump
   ```
   This will:
   - Determine the next version from commit messages since the last tag
   - Update `version` in `pyproject.toml`
   - Update `CHANGELOG.md`
   - Create a commit and git tag (e.g. `v0.4.0`)
3. Review the changes:
   ```sh
   git log -1 --stat
   git diff HEAD~1 CHANGELOG.md
   ```
4. Push the commit and tag:
   ```sh
   git push origin master --follow-tags
   ```
5. The CI `publish` job triggers on the tag, builds the package, and publishes to PyPI via OIDC trusted publisher.

### PyPI trusted publisher setup (one-time)

On [pypi.org](https://pypi.org), under the project's "Publishing" settings, add a new "GitLab" trusted publisher:

- **GitLab instance URL:** `gitlab.com`
- **Project path:** `<your-namespace>/compose-outdated`
- **Environment:** `pypi`
- **Top-level pipeline or workflow name:** `.gitlab-ci.yml`
