Initial
This commit is contained in:
commit
3f60b72c3b
48 changed files with 6599 additions and 0 deletions
41
.dockerignore
Normal file
41
.dockerignore
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Rust
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
test/
|
||||||
|
tests/
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
README.md
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github/
|
||||||
|
.gitlab-ci.yml
|
||||||
|
|
||||||
|
# Runtime files
|
||||||
|
backups/
|
||||||
|
compose-files/
|
||||||
|
config.yaml
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
126
.github/workflows/docker-publish.yml
vendored
Normal file
126
.github/workflows/docker-publish.yml
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
name: Build and Publish Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
tags: ["v*"]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: 1.88
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Cache cargo registry
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}"cargo test --verbose
|
||||||
|
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
build-and-push:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.meta.outputs.version }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Container Registry
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Test Docker image
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
-e RUST_LOG=info \
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} \
|
||||||
|
docker-compose-updater check
|
||||||
|
|
||||||
|
security-scan:
|
||||||
|
needs: build-and-push
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.version }}
|
||||||
|
format: "sarif"
|
||||||
|
output: "trivy-results.sarif"
|
||||||
|
|
||||||
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: "trivy-results.sarif"
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
83
CLAUDE.md
Normal file
83
CLAUDE.md
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Build and Test
|
||||||
|
```bash
|
||||||
|
# Build the project
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run integration tests specifically
|
||||||
|
cargo test --test integration
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
cargo test --coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
```bash
|
||||||
|
# Run a one-time update
|
||||||
|
./target/release/docker-compose-updater --config ./config.yaml update
|
||||||
|
|
||||||
|
# Start the scheduler service
|
||||||
|
./target/release/docker-compose-updater --config ./config.yaml start
|
||||||
|
|
||||||
|
# Run from source in development
|
||||||
|
cargo run -- --config ./config.yaml update
|
||||||
|
cargo run -- --config ./config.yaml start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Testing
|
||||||
|
```bash
|
||||||
|
# Test against example configuration
|
||||||
|
cargo run -- --config ./config.example.yaml update
|
||||||
|
|
||||||
|
# Run in dry-run mode to see what would be updated
|
||||||
|
cargo run -- --config ./demo-config.yaml update # (demo-config.yaml has dry_run: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
This is a Rust-based Docker Compose updater service that automatically updates image versions in Docker Compose files while preserving formatting and comments.
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
- **`main.rs`**: Entry point with CLI argument parsing using clap. Supports `start` (scheduler) and `update` (one-time) commands.
|
||||||
|
- **`config.rs`**: Configuration management with support for multiple registries, update strategies, and image ignore patterns. Loads from `config.yaml` or `/app/config/config.yaml`.
|
||||||
|
- **`compose.rs`**: Docker Compose file parsing and updating. Uses regex-based YAML parsing to preserve formatting and comments while updating image versions.
|
||||||
|
- **`registry.rs`**: Container registry interaction for fetching available image versions. Supports Docker Hub, GitHub Container Registry, GitLab, and custom registries.
|
||||||
|
- **`scheduler.rs`**: Cron-based scheduling system for automatic updates. Runs health checks and reports status.
|
||||||
|
- **`health.rs`**: Health monitoring server that exposes HTTP endpoints for service health checks.
|
||||||
|
|
||||||
|
### Key Design Patterns
|
||||||
|
|
||||||
|
- **Configuration-driven**: All behavior controlled through `config.yaml` including paths, schedules, registries, and update strategies.
|
||||||
|
- **Preserves formatting**: Uses line-by-line regex parsing rather than full YAML parsing to maintain comments and formatting.
|
||||||
|
- **Flexible update strategies**: Supports different version update approaches (latest patch of previous minor, latest patch, latest minor, latest).
|
||||||
|
- **Registry abstraction**: Generic registry client that works with multiple container registries.
|
||||||
|
- **Health monitoring**: Built-in health server for monitoring service status.
|
||||||
|
|
||||||
|
### Update Strategies
|
||||||
|
|
||||||
|
The system supports different update strategies defined in `config.rs`:
|
||||||
|
- `LatestPatchOfPreviousMinor` (default)
|
||||||
|
- `Latest`
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Configuration is loaded from `config.yaml` or `/app/config/config.yaml`
|
||||||
|
- Default configuration includes Docker Hub and GitHub Container Registry
|
||||||
|
- Environment variables like `GITHUB_TOKEN` are used for registry authentication
|
||||||
|
- Images can be ignored using patterns in the `ignore_images` configuration
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Unit tests are embedded in each module using `#[cfg(test)]`
|
||||||
|
- Integration tests are in the `tests/` directory
|
||||||
|
- Uses `tempfile` for testing file operations
|
||||||
|
- Tests cover compose file parsing, image reference parsing, and scheduler creation
|
||||||
2059
Cargo.lock
generated
Normal file
2059
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
34
Cargo.toml
Normal file
34
Cargo.toml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "docker-compose-updater"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Andras Schmelczer <andras@schmelczer.dev>"]
|
||||||
|
description = "Automatically update Docker Compose image versions"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "time", "fs", "net", "io-util"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
semver = "1.0"
|
||||||
|
anyhow = "1.0"
|
||||||
|
regex = "1.10"
|
||||||
|
cron = "0.12"
|
||||||
|
clap = { version = "4.0", features = ["derive"] }
|
||||||
|
async-trait = "0.1"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
chrono = "0.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "docker_compose_updater"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "docker-compose-updater"
|
||||||
|
path = "src/main.rs"
|
||||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
FROM rust:1.85-alpine AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# Install runtime dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
curl
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1000 updater && \
|
||||||
|
adduser -u 1000 -G updater -s /bin/sh -D updater
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
RUN mkdir -p /app/config && \
|
||||||
|
chown -R updater:updater /app
|
||||||
|
|
||||||
|
# Copy binary from builder stage
|
||||||
|
COPY --from=builder /app/target/release/docker-compose-updater /usr/local/bin/docker-compose-updater
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER updater
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Expose health check port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8080/health || exit 1
|
||||||
|
|
||||||
|
CMD ["docker-compose-updater", "--config", "/app/config/config.yaml", "start"]
|
||||||
377
README.md
Normal file
377
README.md
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
# Docker Compose Updater
|
||||||
|
|
||||||
|
A robust, production-ready Rust service that automatically updates Docker Compose image versions whilst preserving comments, formatting, and maintaining operational stability. The service operates on a configurable schedule and uses intelligent update strategies to ensure safe, conservative updates.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- 🔄 **Intelligent Updates**: Automatically updates Docker Compose files using configurable update strategies
|
||||||
|
- 📝 **Format Preservation**: Maintains all comments, whitespace, and YAML formatting perfectly
|
||||||
|
- 🎯 **Conservative Strategies**: Defaults to stable, tested versions rather than bleeding edge
|
||||||
|
- 🏥 **Health Monitoring**: Built-in health endpoints for monitoring and observability
|
||||||
|
- 🔐 **Multi-Registry Support**: Works with Docker Hub, GitHub Container Registry, GitLab, and custom registries
|
||||||
|
- 🔍 **Flexible Filtering**: Configurable patterns to ignore specific images or registries
|
||||||
|
- 🛡️ **Robust Error Handling**: Comprehensive error management with no silent failures
|
||||||
|
- 📋 **Prefix/Suffix Support**: Handles complex versioning schemes like `v1.2.3-alpine`, `release-1.0.0-slim`
|
||||||
|
- 🚀 **Container-Ready**: Designed for containerised environments with proper security
|
||||||
|
- 📊 **Comprehensive Testing**: Extensively tested with 44+ test cases covering edge cases
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Using Docker Compose (Recommended)
|
||||||
|
|
||||||
|
1. **Create a configuration file** (`config.yaml`):
|
||||||
|
```yaml
|
||||||
|
# Paths to scan for Docker Compose files
|
||||||
|
compose_paths:
|
||||||
|
- "./compose-files"
|
||||||
|
- "./docker-compose.yml"
|
||||||
|
- "./services"
|
||||||
|
|
||||||
|
# Cron expression for update schedule
|
||||||
|
schedule: "0 0 2 * * *" # Daily at 2 AM UTC
|
||||||
|
|
||||||
|
# Update strategy (conservative by default)
|
||||||
|
update_strategy: "LatestPatchOfPreviousMinor"
|
||||||
|
|
||||||
|
# Runtime behaviour
|
||||||
|
dry_run: false # Set to true for testing
|
||||||
|
|
||||||
|
# Images to ignore during updates (substring matching)
|
||||||
|
ignore_images:
|
||||||
|
- "localhost"
|
||||||
|
- "127.0.0.1"
|
||||||
|
- "internal.company.com"
|
||||||
|
|
||||||
|
# Registry configurations
|
||||||
|
registries:
|
||||||
|
"docker.io":
|
||||||
|
url: "https://registry-1.docker.io"
|
||||||
|
|
||||||
|
"ghcr.io":
|
||||||
|
url: "https://ghcr.io"
|
||||||
|
auth_token: "${GITHUB_TOKEN}"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy with Docker Compose**:
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
docker-compose-updater:
|
||||||
|
image: ghcr.io/your-username/docker-compose-updater:latest
|
||||||
|
container_name: docker-compose-updater
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||||
|
- RUST_LOG=info
|
||||||
|
volumes:
|
||||||
|
# Mount Docker socket (read-only for security)
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
# Mount compose files directory
|
||||||
|
- ./compose-files:/app/compose-files:rw
|
||||||
|
# Mount configuration
|
||||||
|
- ./config.yaml:/app/config/config.yaml:ro
|
||||||
|
ports:
|
||||||
|
- "8080:8080" # Health monitoring port
|
||||||
|
user: "1000:1000" # Run as non-root user
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start the service**:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Line Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install or build the binary
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Run a one-time update
|
||||||
|
./target/release/docker-compose-updater --config ./config.yaml update
|
||||||
|
|
||||||
|
# Start the scheduled service with health monitoring
|
||||||
|
./target/release/docker-compose-updater --config ./config.yaml start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update Strategies
|
||||||
|
|
||||||
|
The service supports three intelligent update strategies, defaulting to the most conservative:
|
||||||
|
|
||||||
|
|
||||||
|
### `LatestPatchOfPreviousMinor`
|
||||||
|
- **Predictable**: Always updates to the latest patch of the previous minor version
|
||||||
|
- **Consistent Behaviour**: Never updates to the current latest minor
|
||||||
|
- **Example**: `1.5.3` → `1.4.8`
|
||||||
|
|
||||||
|
### `Latest`
|
||||||
|
- **Aggressive**: Updates to the absolute latest available version
|
||||||
|
- **Cutting Edge**: Useful for development environments
|
||||||
|
- **Example**: `1.5.3` → `2.1.0`
|
||||||
|
|
||||||
|
## Advanced Version Handling
|
||||||
|
|
||||||
|
The service handles complex versioning schemes intelligently:
|
||||||
|
|
||||||
|
### Supported Version Formats
|
||||||
|
- **Standard Semantic Versions**: `1.2.3`, `2.0.1`
|
||||||
|
- **Prefixed Versions**: `v1.2.3`, `release-1.0.0`, `build123-2.1.0`
|
||||||
|
- **Suffixed Versions**: `1.2.3-alpine`, `2.0.1-slim`, `1.5.0-ubuntu20.04`
|
||||||
|
- **Combined**: `v1.2.3-alpine-slim`, `release-2.0.1-final`
|
||||||
|
|
||||||
|
### Version Matching Logic
|
||||||
|
The service ensures that prefix and suffix combinations remain consistent:
|
||||||
|
- `v1.2.3-alpine` will only be updated to other `v*.*.*-alpine` versions
|
||||||
|
- `release-1.0.0` will only be updated to other `release-*.*.*` versions
|
||||||
|
- This prevents incompatible image variants from being selected
|
||||||
|
|
||||||
|
## Comprehensive Registry Support
|
||||||
|
|
||||||
|
### Supported Registries
|
||||||
|
- **Docker Hub**: `docker.io` (default registry)
|
||||||
|
- **GitHub Container Registry**: `ghcr.io`
|
||||||
|
- **GitLab Container Registry**: `registry.gitlab.com`
|
||||||
|
- **Google Container Registry**: `gcr.io`
|
||||||
|
- **Amazon ECR**: `*.dkr.ecr.*.amazonaws.com`
|
||||||
|
- **Azure Container Registry**: `*.azurecr.io`
|
||||||
|
- **Custom/Private Registries**: Any registry following Docker Registry API v2
|
||||||
|
|
||||||
|
### Registry Configuration
|
||||||
|
```yaml
|
||||||
|
registries:
|
||||||
|
"docker.io":
|
||||||
|
url: "https://registry-1.docker.io"
|
||||||
|
# Docker Hub typically doesn't require auth for public images
|
||||||
|
|
||||||
|
"ghcr.io":
|
||||||
|
url: "https://ghcr.io"
|
||||||
|
auth_token: "${GITHUB_TOKEN}"
|
||||||
|
|
||||||
|
"registry.gitlab.com":
|
||||||
|
url: "https://registry.gitlab.com"
|
||||||
|
auth_token: "${GITLAB_TOKEN}"
|
||||||
|
|
||||||
|
"your-registry.company.com":
|
||||||
|
url: "https://your-registry.company.com"
|
||||||
|
auth_token: "${COMPANY_REGISTRY_TOKEN}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Image Formats
|
||||||
|
|
||||||
|
The updater handles various image naming conventions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Standard Docker Hub images
|
||||||
|
nginx:1.21.0 → nginx:1.20.6
|
||||||
|
ubuntu:20.04 → ubuntu:18.04
|
||||||
|
|
||||||
|
# Namespaced images
|
||||||
|
bitnami/nginx:1.21.0 → bitnami/nginx:1.20.6
|
||||||
|
library/postgres:13.7 → library/postgres:12.11
|
||||||
|
|
||||||
|
# Custom registries
|
||||||
|
ghcr.io/user/app:v1.0.0 → ghcr.io/user/app:v0.9.5
|
||||||
|
registry.example.com/team/service:2.1.3 → registry.example.com/team/service:2.0.9
|
||||||
|
|
||||||
|
# Complex versioning
|
||||||
|
nginx:1.21.0-alpine → nginx:1.20.6-alpine
|
||||||
|
postgres:13.7-bullseye → postgres:12.11-bullseye
|
||||||
|
redis:v7.0.0-alpine3.16 → redis:v6.2.7-alpine3.16
|
||||||
|
|
||||||
|
# Local development (typically ignored)
|
||||||
|
localhost:5000/myapp:latest → [ignored by default]
|
||||||
|
127.0.0.1:8080/service:dev → [ignored by default]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Monitoring & Observability
|
||||||
|
|
||||||
|
The service provides comprehensive monitoring capabilities:
|
||||||
|
|
||||||
|
### Health Endpoints
|
||||||
|
- **`GET /`**: Basic health check
|
||||||
|
- Returns `{"status":"healthy"}` when operational
|
||||||
|
- Returns `{"status":"unhealthy"}` during failures
|
||||||
|
- Includes basic service information
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
```bash
|
||||||
|
# Configure logging level via environment variable
|
||||||
|
RUST_LOG=debug # For detailed debugging
|
||||||
|
RUST_LOG=info # For operational information (default)
|
||||||
|
RUST_LOG=warn # For warnings and errors only
|
||||||
|
RUST_LOG=error # For errors only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Monitoring
|
||||||
|
The service reports:
|
||||||
|
- Successful update cycles with file counts
|
||||||
|
- Failed updates with detailed error information
|
||||||
|
- Registry connectivity status
|
||||||
|
- Configuration validation results
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
- **Non-Root Execution**: Designed to run as non-root user in containers
|
||||||
|
- **Minimal Attack Surface**: Alpine Linux base image with minimal packages
|
||||||
|
- **Read-Only Docker Socket**: Only requires read access to Docker socket
|
||||||
|
- **Secret Management**: All tokens provided via environment variables
|
||||||
|
- **Input Validation**: Comprehensive validation of all configuration inputs
|
||||||
|
- **Error Sanitisation**: No sensitive information leaked in error messages
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/your-username/docker-compose-updater.git
|
||||||
|
cd docker-compose-updater
|
||||||
|
|
||||||
|
# Build in release mode
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Run comprehensive test suite
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run with custom configuration
|
||||||
|
./target/release/docker-compose-updater --config ./config.example.yaml start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
The project includes extensive testing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests (44+ test cases)
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run specific test suites
|
||||||
|
cargo test --test integration_tests # Integration tests
|
||||||
|
cargo test --test test_error_handling # Error handling tests
|
||||||
|
cargo test --test test_config_validation # Configuration tests
|
||||||
|
cargo test --test test_compose_operations # Compose file operations
|
||||||
|
|
||||||
|
# Run with output for debugging
|
||||||
|
cargo test -- --nocapture
|
||||||
|
|
||||||
|
# Generate coverage report (requires cargo-llvm-cov)
|
||||||
|
cargo llvm-cov --html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
- **Error Handling**: Invalid configurations, malformed files, network failures
|
||||||
|
- **Config Validation**: All configuration formats, edge cases, defaults
|
||||||
|
- **Compose Operations**: Complex file formats, image parsing, version handling
|
||||||
|
- **Registry Integration**: Multiple registry types, authentication, failures
|
||||||
|
- **Version Logic**: All update strategies, complex versioning schemes
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
### Complete Configuration Example
|
||||||
|
```yaml
|
||||||
|
# File/directory paths to scan for Docker Compose files
|
||||||
|
compose_paths:
|
||||||
|
- "/app/compose-files" # Directory to scan recursively
|
||||||
|
- "./docker-compose.yml" # Specific file
|
||||||
|
- "./services/" # Another directory
|
||||||
|
|
||||||
|
# Cron expression for update schedule (UTC timezone)
|
||||||
|
schedule: "0 0 2 * * *" # Daily at 2 AM
|
||||||
|
|
||||||
|
# Update strategy selection
|
||||||
|
update_strategy: "LatestPatchOfPreviousMinor"
|
||||||
|
|
||||||
|
# Prevent actual file modifications (for testing)
|
||||||
|
dry_run: false
|
||||||
|
|
||||||
|
# Images to ignore during updates (substring matching)
|
||||||
|
ignore_images:
|
||||||
|
- "localhost" # Local development images
|
||||||
|
- "127.0.0.1" # Local registry
|
||||||
|
- "internal.company.com" # Private internal registry
|
||||||
|
- "snapshot" # Snapshot versions
|
||||||
|
|
||||||
|
# Registry configurations
|
||||||
|
registries:
|
||||||
|
"docker.io":
|
||||||
|
url: "https://registry-1.docker.io"
|
||||||
|
# auth_token not typically needed for public images
|
||||||
|
|
||||||
|
"ghcr.io":
|
||||||
|
url: "https://ghcr.io"
|
||||||
|
auth_token: "${GITHUB_TOKEN}"
|
||||||
|
|
||||||
|
"registry.gitlab.com":
|
||||||
|
url: "https://registry.gitlab.com"
|
||||||
|
auth_token: "${GITLAB_TOKEN}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
- **`GITHUB_TOKEN`**: Personal access token for GitHub Container Registry
|
||||||
|
- **`GITLAB_TOKEN`**: Access token for GitLab Container Registry
|
||||||
|
- **`RUST_LOG`**: Logging level (`debug`, `info`, `warn`, `error`)
|
||||||
|
- **Registry-specific tokens**: For private registries
|
||||||
|
|
||||||
|
## Operational Considerations
|
||||||
|
|
||||||
|
### Backup Strategy
|
||||||
|
- Always backup your Docker Compose files before running updates
|
||||||
|
- Use version control (Git) to track changes
|
||||||
|
- Test with `dry_run: true` before enabling actual updates
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
- Monitor the health endpoint for service availability
|
||||||
|
- Set up alerts for failed update cycles
|
||||||
|
- Review logs regularly for registry connectivity issues
|
||||||
|
|
||||||
|
### Resource Requirements
|
||||||
|
- **Memory**: ~10-50MB during operation
|
||||||
|
- **CPU**: Minimal during dormant periods, brief spikes during updates
|
||||||
|
- **Network**: Requires outbound HTTPS access to configured registries
|
||||||
|
- **Storage**: Minimal, only for configuration and temporary files
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Service fails to start**
|
||||||
|
- Check configuration file syntax with `yamllint`
|
||||||
|
- Verify file paths exist and are accessible
|
||||||
|
- Ensure cron expression is valid
|
||||||
|
|
||||||
|
**Registry authentication failures**
|
||||||
|
- Verify environment variables are set correctly
|
||||||
|
- Check token permissions for the target registry
|
||||||
|
- Test registry connectivity manually
|
||||||
|
|
||||||
|
**Updates not applied**
|
||||||
|
- Check if `dry_run` is enabled
|
||||||
|
- Verify images are not in the ignore list
|
||||||
|
- Ensure semantic versioning is used in image tags
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Write tests for your changes
|
||||||
|
4. Ensure all tests pass (`cargo test`)
|
||||||
|
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
This project is licensed under the MIT Licence - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v0.1.0
|
||||||
|
- Initial release with comprehensive functionality
|
||||||
|
- Multi-registry support (Docker Hub, GHCR, GitLab, custom)
|
||||||
|
- Intelligent update strategies with conservative defaults
|
||||||
|
- Comment and formatting preservation
|
||||||
|
- Comprehensive error handling (no silent failures)
|
||||||
|
- Health monitoring and observability
|
||||||
|
- Extensive test coverage (44+ test cases)
|
||||||
|
- Prefix/suffix version handling
|
||||||
|
- Robust configuration validation
|
||||||
|
- Production-ready security features
|
||||||
4
compose/backup.yml
Normal file
4
compose/backup.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
backup:
|
||||||
|
image: ghcr.io/schmelczer/backup-container:v0.0.3
|
||||||
11
compose/calibre.yml
Normal file
11
compose/calibre.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
audiobookshelf:
|
||||||
|
image: advplyr/audiobookshelf:2.26.0
|
||||||
|
|
||||||
|
calibre-web:
|
||||||
|
image: "" # required for wud
|
||||||
|
build:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
local-network:
|
||||||
10
compose/cloud.yml
Normal file
10
compose/cloud.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
filebrowser:
|
||||||
|
image: filebrowser/filebrowser:v2.40.1
|
||||||
|
|
||||||
|
syncthing:
|
||||||
|
image: syncthing/syncthing:1.30.0
|
||||||
|
|
||||||
|
networks:
|
||||||
|
local-network:
|
||||||
4
compose/dns.yml
Normal file
4
compose/dns.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
dns-server:
|
||||||
|
image: technitium/dns-server:13.6.0
|
||||||
39
compose/frontdoor.yml
Normal file
39
compose/frontdoor.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
authelia:
|
||||||
|
image: authelia/authelia:4.39.5
|
||||||
|
|
||||||
|
certbot:
|
||||||
|
image: certbot/dns-digitalocean:v4.1.1
|
||||||
|
|
||||||
|
dyndns:
|
||||||
|
build:
|
||||||
|
context: /volumes/dyndns
|
||||||
|
container_name: frontdoor_dyndns
|
||||||
|
environment:
|
||||||
|
TZ: $TIME_ZONE
|
||||||
|
DOMAIN: schmelczer.dev
|
||||||
|
NAME: home;cloud;dns;immich;declared;pdf;homeassistant;store;audiobook;books;movies;stats;wud;auth;paperless;minecraft;torrent;obsidian;ghostfolio;vpn;mealie
|
||||||
|
DIGITALOCEAN_TOKEN: $DIGITALOCEAN_TOKEN
|
||||||
|
networks:
|
||||||
|
- local-network
|
||||||
|
restart: unless-stopped
|
||||||
|
init: true
|
||||||
|
tty: true
|
||||||
|
|
||||||
|
fail2ban:
|
||||||
|
image: ghcr.io/crazy-max/fail2ban:1.1.0
|
||||||
|
container_name: frontdoor_fail2ban
|
||||||
|
volumes:
|
||||||
|
- /volumes/fail2ban:/data
|
||||||
|
- /volumes/nginx/logs:/var/log:ro
|
||||||
|
- /volumes/vaultwarden/data/vaultwarden.log:/vaultwarden/vaultwarden.log:ro
|
||||||
|
environment:
|
||||||
|
TZ: $TIME_ZONE
|
||||||
|
F2B_LOG_TARGET: STDOUT
|
||||||
|
|
||||||
|
tinyauth:
|
||||||
|
image: ghcr.io/steveiliop56/tinyauth:v3.6.2
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.29.0
|
||||||
20
compose/github-runner.yml
Normal file
20
compose/github-runner.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
github-runner:
|
||||||
|
build:
|
||||||
|
context: /volumes/github-runner
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
- REPO_URL=https://github.com/schmelczer/vault-link
|
||||||
|
- ACCESS_TOKEN=$GITHUB_ACTIONS_RUNNER_TOKEN
|
||||||
|
restart: unless-stopped
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '6'
|
||||||
|
memory: '8G'
|
||||||
|
mode: replicated
|
||||||
|
replicas: 3
|
||||||
|
init: true
|
||||||
|
tty: true
|
||||||
4
compose/homeassistant.yml
Normal file
4
compose/homeassistant.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
homeassistant:
|
||||||
|
image: homeassistant/home-assistant:2025.7.2
|
||||||
17
compose/immich.yml
Normal file
17
compose/immich.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
||||||
|
|
||||||
|
immich-folder-album-creator:
|
||||||
|
image: salvoxia/immich-folder-album-creator:0.19.0
|
||||||
|
|
||||||
|
immich-machine-learning:
|
||||||
|
image: ghcr.io/immich-app/immich-machine-learning:v1.135.3
|
||||||
|
|
||||||
|
nas_sync:
|
||||||
|
build:
|
||||||
|
context: /volumes/nas-sync
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8
|
||||||
22
compose/media.yml
Normal file
22
compose/media.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
gluetun:
|
||||||
|
image: qmcgaw/gluetun:v3.40.0
|
||||||
|
|
||||||
|
jellyfin:
|
||||||
|
image: jellyfin/jellyfin:10.10.7
|
||||||
|
|
||||||
|
jellyseerr:
|
||||||
|
image: fallenbagel/jellyseerr:2.7.1
|
||||||
|
|
||||||
|
prowlarr:
|
||||||
|
image: lscr.io/linuxserver/prowlarr:1.37.0
|
||||||
|
|
||||||
|
qbittorrent:
|
||||||
|
image: lscr.io/linuxserver/qbittorrent:5.1.2-libtorrentv1
|
||||||
|
|
||||||
|
radarr:
|
||||||
|
image: lscr.io/linuxserver/radarr:5.26.2
|
||||||
|
|
||||||
|
sonarr:
|
||||||
|
image: lscr.io/linuxserver/sonarr:4.0.15
|
||||||
4
compose/minecraft.yml
Normal file
4
compose/minecraft.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
minecraft_andris:
|
||||||
|
image: itzg/minecraft-server:2025.6.2-java21-graalvm
|
||||||
4
compose/obsidian.yml
Normal file
4
compose/obsidian.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
obsidian:
|
||||||
|
image: ghcr.io/schmelczer/vault-link:0.5.1
|
||||||
19
compose/paperless.yml
Normal file
19
compose/paperless.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
name: active
|
||||||
|
services:
|
||||||
|
paperless-ai:
|
||||||
|
image: clusterzx/paperless-ai:3.0.7
|
||||||
|
|
||||||
|
paperless-broker:
|
||||||
|
image: redis:8.0.3-alpine
|
||||||
|
|
||||||
|
paperless-gotenberg:
|
||||||
|
image: gotenberg/gotenberg:8.21.1
|
||||||
|
|
||||||
|
paperless-ollama:
|
||||||
|
image: ollama/ollama:0.9.6
|
||||||
|
|
||||||
|
paperless-tika:
|
||||||
|
image: apache/tika:3.2.1.0-full
|
||||||
|
|
||||||
|
paperless-webserver:
|
||||||
|
image: ghcr.io/paperless-ngx/paperless-ngx:2.17.1
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue