Skip to main content
Glama

MCP Memory Service

cleanup-images.yml.disabled14.8 kB
name: Cleanup Old Docker Images on: # Run after successful releases workflow_run: workflows: ["Main CI/CD Pipeline", "Docker Publish (Tags)", "Publish and Test (Tags)"] types: - completed # Run weekly on Sunday at 2 AM UTC schedule: - cron: '0 2 * * 0' # Allow manual trigger with options workflow_dispatch: inputs: dry_run: description: 'Dry run (no deletions)' required: false type: boolean default: true keep_versions: description: 'Number of versions to keep' required: false type: string default: '5' delete_untagged: description: 'Delete untagged images' required: false type: boolean default: true env: DOCKER_IMAGE: doobidoo/mcp-memory-service GHCR_IMAGE: ghcr.io/doobidoo/mcp-memory-service jobs: # Cleanup GitHub Container Registry images cleanup-ghcr: runs-on: ubuntu-latest name: Cleanup GHCR Images permissions: packages: write contents: read steps: - name: Checkout repository uses: actions/checkout@v4 - name: Delete untagged images from GHCR if: github.event.inputs.delete_untagged != 'false' uses: actions/delete-package-versions@v4 continue-on-error: true with: package-name: 'mcp-memory-service' package-type: 'container' token: ${{ secrets.GITHUB_TOKEN }} min-versions-to-keep: 0 delete-only-untagged-versions: 'true' - name: Clean old GHCR versions (keep last N) uses: actions/delete-package-versions@v4 continue-on-error: true with: package-name: 'mcp-memory-service' package-type: 'container' token: ${{ secrets.GITHUB_TOKEN }} min-versions-to-keep: ${{ github.event.inputs.keep_versions || '5' }} ignore-versions: '^(latest|slim|main|v[0-9]+\.[0-9]+\.[0-9]+)$' delete-only-pre-release-versions: 'false' - name: Clean buildcache tags older than 7 days uses: snok/container-retention-policy@v1 continue-on-error: true with: image-names: mcp-memory-service cut-off: 7 days ago UTC account-type: personal token: ${{ secrets.GITHUB_TOKEN }} filter-tags: 'buildcache-*' filter-include-untagged: false skip-tags: latest,slim,main dry-run: ${{ github.event.inputs.dry_run || 'false' }} # Cleanup Docker Hub images cleanup-dockerhub: runs-on: ubuntu-latest name: Cleanup Docker Hub Images if: github.event_name != 'pull_request' steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.11' cache: 'pip' cache-dependency-path: '**/pyproject.toml' - name: Install dependencies run: | pip install requests python-dateutil - name: Validate Docker Hub credentials run: | if [ -z "${{ secrets.DOCKER_USERNAME }}" ] || [ -z "${{ secrets.DOCKER_PASSWORD }}" ]; then echo "⚠️ Docker Hub credentials not available, skipping Docker Hub cleanup" echo "SKIP_DOCKER_CLEANUP=true" >> $GITHUB_ENV else echo "✓ Docker Hub credentials available" echo "SKIP_DOCKER_CLEANUP=false" >> $GITHUB_ENV fi - name: Create cleanup script run: | cat > cleanup_docker_hub.py << 'EOF' #!/usr/bin/env python3 """ Docker Hub image cleanup script Deletes old image tags based on retention policy """ import os import sys import json import requests from datetime import datetime, timedelta, timezone from dateutil import parser import re class DockerHubCleaner: def __init__(self, username, password, repository): self.username = username self.password = password self.repository = repository self.token = None self.api_base = "https://hub.docker.com/v2" def authenticate(self): """Get authentication token from Docker Hub""" url = f"{self.api_base}/users/login/" data = {"username": self.username, "password": self.password} try: response = requests.post(url, json=data) response.raise_for_status() self.token = response.json()["token"] print("✓ Authenticated with Docker Hub") return True except Exception as e: print(f"✗ Authentication failed: {e}") return False def get_tags(self): """Get all tags for the repository""" url = f"{self.api_base}/repositories/{self.repository}/tags" headers = {"Authorization": f"Bearer {self.token}"} tags = [] while url: try: response = requests.get(url, headers=headers, params={"page_size": 100}) response.raise_for_status() data = response.json() tags.extend(data["results"]) url = data.get("next") except Exception as e: print(f"✗ Failed to get tags: {e}") break print(f"✓ Found {len(tags)} tags in repository") return tags def should_keep_tag(self, tag_name, tag_date, keep_versions, cutoff_date): """Determine if a tag should be kept based on retention policy""" # Always keep these tags protected_tags = ["latest", "slim", "main", "stable"] if tag_name in protected_tags: return True, "Protected tag" # Keep semantic version tags (v1.2.3) if re.match(r'^v?\d+\.\d+\.\d+$', tag_name): # Check if it's one of the most recent versions return True, "Semantic version" # Keep major.minor tags (1.0, 2.1) if re.match(r'^v?\d+\.\d+$', tag_name): return True, "Major.minor version" # Delete buildcache tags older than cutoff if tag_name.startswith("buildcache-"): if tag_date < cutoff_date: return False, "Old buildcache tag" return True, "Recent buildcache tag" # Delete sha/digest tags older than cutoff if tag_name.startswith("sha256-") or len(tag_name) == 7: if tag_date < cutoff_date: return False, "Old sha/digest tag" return True, "Recent sha/digest tag" # Delete test/dev tags older than cutoff if any(x in tag_name.lower() for x in ["test", "dev", "tmp", "temp"]): if tag_date < cutoff_date: return False, "Old test/dev tag" return True, "Recent test/dev tag" # Keep if recent if tag_date >= cutoff_date: return True, "Recent tag" return False, "Old tag" def delete_tag(self, tag_name, dry_run=False): """Delete a specific tag from Docker Hub""" if dry_run: print(f" [DRY RUN] Would delete: {tag_name}") return True url = f"{self.api_base}/repositories/{self.repository}/tags/{tag_name}/" headers = {"Authorization": f"Bearer {self.token}"} try: response = requests.delete(url, headers=headers) response.raise_for_status() print(f" ✓ Deleted: {tag_name}") return True except Exception as e: print(f" ✗ Failed to delete {tag_name}: {e}") return False def cleanup(self, keep_versions=5, days_to_keep=30, dry_run=False): """Main cleanup function""" if not self.authenticate(): return False tags = self.get_tags() if not tags: return False # Sort tags by date (newest first) tags.sort(key=lambda x: parser.parse(x["last_updated"]), reverse=True) # Calculate cutoff date cutoff_date = datetime.now(timezone.utc) - timedelta(days=days_to_keep) # Separate semantic versions from other tags version_tags = [] other_tags = [] for tag in tags: if re.match(r'^v?\d+\.\d+\.\d+$', tag["name"]): version_tags.append(tag) else: other_tags.append(tag) print(f"\n📊 Repository Statistics:") print(f" - Total tags: {len(tags)}") print(f" - Version tags: {len(version_tags)}") print(f" - Other tags: {len(other_tags)}") print(f" - Keep versions: {keep_versions}") print(f" - Days to keep: {days_to_keep}") print(f" - Dry run: {dry_run}") deleted_count = 0 kept_count = 0 print(f"\n🔍 Analyzing tags...") # Process version tags (keep only the most recent N) for i, tag in enumerate(version_tags): tag_date = parser.parse(tag["last_updated"]) if i < keep_versions: print(f" ✓ Keep {tag['name']} (recent version #{i+1})") kept_count += 1 else: print(f" ✗ Delete {tag['name']} (old version #{i+1})") if self.delete_tag(tag["name"], dry_run): deleted_count += 1 # Process other tags based on policy for tag in other_tags: tag_date = parser.parse(tag["last_updated"]) should_keep, reason = self.should_keep_tag( tag["name"], tag_date, keep_versions, cutoff_date ) if should_keep: print(f" ✓ Keep {tag['name']} ({reason})") kept_count += 1 else: print(f" ✗ Delete {tag['name']} ({reason})") if self.delete_tag(tag["name"], dry_run): deleted_count += 1 print(f"\n📈 Summary:") print(f" - Kept: {kept_count} tags") print(f" - Deleted: {deleted_count} tags") return True def main(): # Get environment variables username = os.environ.get("DOCKER_USERNAME") password = os.environ.get("DOCKER_PASSWORD") repository = os.environ.get("DOCKER_REPOSITORY", "doobidoo/mcp-memory-service") # Get parameters dry_run = os.environ.get("DRY_RUN", "false").lower() == "true" keep_versions = int(os.environ.get("KEEP_VERSIONS", "5")) days_to_keep = int(os.environ.get("DAYS_TO_KEEP", "30")) if not username or not password: print("✗ Missing DOCKER_USERNAME or DOCKER_PASSWORD") sys.exit(1) print(f"🐳 Docker Hub Cleanup for {repository}") print("=" * 50) cleaner = DockerHubCleaner(username, password, repository) success = cleaner.cleanup(keep_versions, days_to_keep, dry_run) sys.exit(0 if success else 1) if __name__ == "__main__": main() EOF - name: Run Docker Hub cleanup if: env.SKIP_DOCKER_CLEANUP != 'true' env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_REPOSITORY: doobidoo/mcp-memory-service DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} KEEP_VERSIONS: ${{ github.event.inputs.keep_versions || '5' }} DAYS_TO_KEEP: '30' run: python cleanup_docker_hub.py - name: Docker Hub cleanup skipped if: env.SKIP_DOCKER_CLEANUP == 'true' run: echo "⚠️ Docker Hub cleanup was skipped due to missing credentials" # Report cleanup results report: needs: [cleanup-ghcr, cleanup-dockerhub] runs-on: ubuntu-latest if: always() name: Cleanup Report steps: - name: Generate cleanup report run: | echo "## 🧹 Docker Image Cleanup Report" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Run Type**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY echo "**Dry Run**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY echo "**Keep Versions**: ${{ github.event.inputs.keep_versions || '5' }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Results" >> $GITHUB_STEP_SUMMARY echo "- GHCR Cleanup: ${{ needs.cleanup-ghcr.result }}" >> $GITHUB_STEP_SUMMARY echo "- Docker Hub Cleanup: ${{ needs.cleanup-dockerhub.result }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Retention Policy" >> $GITHUB_STEP_SUMMARY echo "- **Protected tags**: latest, slim, main, stable" >> $GITHUB_STEP_SUMMARY echo "- **Version tags**: Keep last ${{ github.event.inputs.keep_versions || '5' }} versions" >> $GITHUB_STEP_SUMMARY echo "- **Build cache**: Delete after 7 days" >> $GITHUB_STEP_SUMMARY echo "- **Test/dev tags**: Delete after 30 days" >> $GITHUB_STEP_SUMMARY echo "- **Untagged images**: Delete immediately" >> $GITHUB_STEP_SUMMARY

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/doobidoo/mcp-memory-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server