A lightweight CLI tool that automates container image updates in GitOps repositories. Replace image tags and/or digests across multiple GitHub repositories with a single command, enabling automated deployment workflows.
- Flexible Modes: Dry-run for validation, apply mode for commits
- CI/CD Integration: Built-in CI mode with
GIT_REFpattern matching - Complete Image Support: Handles tags, digests, and combined references (e.g.,
registry.io/ns/app:1.2.3@sha256:...) - Multiple Repositories: Update images across any number of repos and files
- Configuration Formats: JSON (default) and YAML support
- Performance Optimized: Response caching eliminates duplicate API calls
- Safe Operations: Regex escaping prevents unintended replacements
- Robust HTTP: Automatic retries, timeouts, and error handling
- Clean Logging: Minimal output by default, verbose mode for debugging
- Python 3.8+
- A GitHub/GitHub Enterprise token with content read/write access
# Install from PyPI
pip install gitops-image-replacer
# Verify installation
gitops-image-replacer --help# Clone repository
git clone https://github.com/slauger/gitops-image-replacer.git
cd gitops-image-replacer
# Install in development mode
pip install -e .
# Or run directly
python -m gitops_image_replacer --help- Create a configuration file (default:
gitops-image-replacer.json). - Run a dry-run:
gitops-image-replacer docker.io/example/app:2.0.0 gitops-image-replacer docker.io/example/app:2.0.0@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 gitops-image-replacer docker.io/example/app@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 gitops-image-replacer --config gitops-image-replacer.json docker.io/example/app:2.0.0
- Apply changes (commit to target repos):
gitops-image-replacer --apply docker.io/example/app:2.0.0 gitops-image-replacer --config gitops-image-replacer.json --apply docker.io/example/app:2.0.0
usage: gitops-image-replacer [-h] [--config <file>] [--apply] [--ci]
[--name <string>] [--email <string>]
[--message <string>] [--api <string>]
[--verbose]
<string>
--configPath to the configuration file (default:gitops-image-replacer.json). JSON recommended.--applyApply changes (commit). Without this flag the tool runs in dry-run.--ciCI mode: validatesGIT_REFagainstwhen/exceptregex patterns from config.--nameCommit author name (default: envGIT_COMMIT_NAMEorReplacer Bot).--emailCommit author email (default: envGIT_COMMIT_EMAILorreplacer-bot@localhost.localdomain).--messageCommit message template (default:fix: update image to {}).--apiGitHub API URL (default: envGITHUB_API_URLorhttps://github.com/api/v3).--verbosePrint file contents and desired state (use with care in CI logs).- Positional:
image(e.g.,docker.io/foo/bar:2.0.0). Tag and digest are optional.
GITHUB_TOKEN(required) – token with access to read/write repository contents.GIT_REF(required when--ci) – the current ref string, e.g.,refs/heads/main.
Recommended token scopes:
- Public repos only:
public_repo - Private repos:
repo - GitHub Enterprise: equivalent content permissions
Default format is JSON. YAML (.yaml/.yml) is supported as well.
{
"gitops-image-replacer": [
{
"repository": "org/repo",
"branch": "main",
"file": "path/to/values.yaml",
"when": "^refs/heads/(main|release/.*)$",
"except": "^refs/heads/feature/"
}
]
}Fields
repository(string):ORG/REPOpath on GitHub/GHEbranch(string): target branchfile(string): target file path in the repositorywhen(string, optional): regex that must matchGIT_REFwhen--ciis enabledexcept(string, optional): regex that must not matchGIT_REFwhen--ciis enabled
The tool uses
re.match(anchored at the string start). Use^...$in your patterns if you require a full match.
JSON (default)
{
"gitops-image-replacer": [
{
"repository": "acme/online-shop",
"branch": "main",
"file": "deploy/values.yaml",
"when": "^refs/heads/(main|release/.*)$"
},
{
"repository": "acme/payments",
"branch": "develop",
"file": "charts/payments/values.yaml",
"except": "^refs/heads/legacy/"
}
]
}YAML (alternative)
gitops-image-replacer:
- repository: acme/online-shop
branch: main
file: deploy/values.yaml
when: '^refs/heads/(main|release/.*)$'
- repository: acme/payments
branch: develop
file: charts/payments/values.yaml
except: '^refs/heads/legacy/'The tool validates and matches container images using a comprehensive regex pattern. All components are optional except the image name.
(?P<repository>[\w.\-_]+((?::\d+|)(?=/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+))|)(?:/|)(?P<image>[a-zA-Z0-9.\-_]+(?:/[a-zA-Z0-9.\-_]+|))(:(?P<tag>[\w.\-_]{1,127})|)?(@(?P<digest>sha256:[a-f0-9]{64}))?- Repository/Registry (optional):
docker.io,gcr.io,registry.example.com:5000- Supports hostnames with optional ports
- Alphanumeric, dots, dashes, underscores
- Image Name (required):
library/nginx,myorg/myapp,MyApp- Case-sensitive (supports both uppercase and lowercase)
- Can include organization/namespace
- Tag (optional):
:latest,:v1.2.3,:20241125-abc123- Max 127 characters
- Alphanumeric, dots, dashes, underscores
- Digest (optional):
@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1- SHA256 hash (64 hex characters, lowercase)
# Simple image name
nginx
# With registry and tag
docker.io/library/nginx:1.25
# With digest only
gcr.io/myproject/myapp@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
# Tag and digest (immutable reference)
registry.example.com:5000/org/app:v2.0.0@sha256:abc...
# Case-sensitive names
ghcr.io/MyOrg/MyApp:latest- Validation: Checks CLI arguments, environment variables, and configuration file
- Precheck Phase: Validates access to all target repositories/files (caches responses)
- Replace Phase: Downloads files (reuses cached data), performs safe regex replacement
- Commit Phase: If
--applyis set and changes detected, commits via GitHub Contents API - Exit Codes: Returns
0on success, non-zero on failures
The tool caches file contents from the precheck phase, eliminating duplicate API calls during the replace phase. This reduces GitHub API usage by approximately 50% in typical scenarios.
0success (no changes or committed changes)1validation or API error
- Always start with dry-run: Test your configuration without
--applyfirst - Use verbose mode carefully:
--verboseprints file contents - avoid in CI logs with sensitive data - Validate regex patterns: Use
^...$anchors inwhen/exceptfor full string matches
- Keep config versioned: Store
gitops-image-replacer.jsonin your repository - Use meaningful names: Standard naming makes the file's purpose obvious
- Document patterns: Comment your regex patterns (especially in CI mode)
- Limit token scope: Use minimal permissions (contents: write)
- Rotate tokens: Regularly update GitHub tokens
- Review changes: Use dry-run before production deployments
- Minimize targets: Only configure files that need updates
- Use CI patterns wisely:
when/exceptpatterns reduce unnecessary runs - Leverage caching: The tool automatically caches API responses
401 Unauthorized
- Verify
GITHUB_TOKENis set correctly - Check token has
repoorpublic_reposcope - For GitHub Enterprise, confirm token has access to the organization
404 Not Found
- Verify
repository,branch, andfilepaths in config - Check branch name spelling (case-sensitive)
- Ensure file exists at the specified path
No changes detected
- Confirm target file contains the exact image name (case-sensitive)
- Tags and digests are optional in the search pattern
- Use
--verboseto see file contents and verify the image reference format
Rate limiting (429/403)
- Built-in retries handle temporary rate limits
- Reduce execution frequency in CI/CD pipelines
- Consider using GitHub App tokens for higher limits
Run with --verbose to see:
- Full API URLs being called
- Complete file contents before replacement
- Desired file contents after replacement
Warning: Verbose mode may expose sensitive data in logs.
Update production manifests when a new image is built:
# In your CI/CD pipeline after building image
gitops-image-replacer --apply docker.io/myorg/myapp:${CI_COMMIT_SHA}Use CI mode to update different environments based on branch:
{
"gitops-image-replacer": [
{
"repository": "myorg/gitops-production",
"branch": "main",
"file": "apps/myapp/deployment.yaml",
"when": "^refs/heads/main$"
},
{
"repository": "myorg/gitops-staging",
"branch": "main",
"file": "apps/myapp/deployment.yaml",
"when": "^refs/heads/(main|release/.*)$"
}
]
}Update images with immutable digest references:
gitops-image-replacer --apply \
registry.example.com/myapp:v2.0.0@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1Contributions are welcome! Please ensure:
- Code follows existing style and patterns
- Changes are tested with both dry-run and apply modes
- Documentation is updated for new features
This project is licensed under the MIT License - see the LICENSE file for details.