Skip to content

Daily Docker Image Cleanup #134

Daily Docker Image Cleanup

Daily Docker Image Cleanup #134

name: "Daily Docker Image Cleanup"
on:
schedule:
# Run daily at 2:00 AM UTC
- cron: "0 2 * * *"
workflow_dispatch:
jobs:
cleanup-images:
name: Clean up old Docker images
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Delete old Docker images
uses: actions/github-script@v7
with:
script: |
const repo = context.repo.repo;
const owner = context.repo.owner;
try {
// Get all package versions for the repository
const packages = await github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({
package_type: 'container',
package_name: repo,
org: owner,
per_page: 100
});
console.log(`Found ${packages.data.length} package versions to evaluate`);
let deletedCount = 0;
for (const pkg of packages.data) {
const tags = pkg.metadata?.container?.tags || [];
const createdAt = new Date(pkg.created_at);
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
// Delete if no tags (untagged packages should be removed)
if (tags.length === 0) {
console.log(`Deleting untagged package ${pkg.id} (created: ${createdAt.toISOString()})`);
try {
await github.rest.packages.deletePackageVersionForOrg({
package_type: 'container',
package_name: repo,
org: owner,
package_version_id: pkg.id
});
console.log(`Deleted untagged package ${pkg.id}`);
deletedCount++;
} catch (deleteError) {
console.log(`Failed to delete untagged package ${pkg.id}:`, deleteError.message);
}
continue;
}
// Check if any tag matches our keep patterns
const hasKeepTags = tags.some(tag =>
tag === 'latest' ||
tag.startsWith('pr-') ||
tag.startsWith('sha-')
);
// Keep the package if it has keep tags AND is less than 3 months old
const shouldKeep = hasKeepTags && createdAt > threeMonthsAgo;
if (shouldKeep) {
console.log(`Keeping package ${pkg.id} with tags: ${tags.join(', ')} (created: ${createdAt.toISOString()})`);
continue;
}
if (hasKeepTags && createdAt <= threeMonthsAgo) {
console.log(`Deleting old package ${pkg.id} with tags: ${tags.join(', ')} (created: ${createdAt.toISOString()}, older than 3 months)`);
} else {
console.log(`Deleting package ${pkg.id} with non-keep tags: ${tags.join(', ')}`);
}
// Delete the package version
try {
await github.rest.packages.deletePackageVersionForOrg({
package_type: 'container',
package_name: repo,
org: owner,
package_version_id: pkg.id
});
console.log(`Deleted package ${pkg.id} with tags: ${tags.join(', ')}`);
deletedCount++;
} catch (deleteError) {
console.log(`Failed to delete package ${pkg.id}:`, deleteError.message);
}
}
console.log(`Cleanup completed. Deleted ${deletedCount} package versions.`);
} catch (error) {
// Handle org vs user owned repos
if (error.status === 404) {
console.log('Trying user-owned package cleanup...');
try {
const packages = await github.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
package_type: 'container',
package_name: repo,
username: owner,
per_page: 100
});
console.log(`Found ${packages.data.length} package versions to evaluate`);
let deletedCount = 0;
for (const pkg of packages.data) {
const tags = pkg.metadata?.container?.tags || [];
const createdAt = new Date(pkg.created_at);
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
// Delete if no tags (untagged packages should be removed)
if (tags.length === 0) {
console.log(`Deleting untagged package ${pkg.id} (created: ${createdAt.toISOString()})`);
try {
await github.rest.packages.deletePackageVersionForUser({
package_type: 'container',
package_name: repo,
username: owner,
package_version_id: pkg.id
});
console.log(`Deleted untagged package ${pkg.id}`);
deletedCount++;
} catch (deleteError) {
console.log(`Failed to delete untagged package ${pkg.id}:`, deleteError.message);
}
continue;
}
// Check if any tag matches our keep patterns
const hasKeepTags = tags.some(tag =>
tag === 'latest' ||
tag.startsWith('pr-') ||
tag.startsWith('sha-')
);
// Keep the package if it has keep tags AND is less than 3 months old
const shouldKeep = hasKeepTags && createdAt > threeMonthsAgo;
if (shouldKeep) {
console.log(`Keeping package ${pkg.id} with tags: ${tags.join(', ')} (created: ${createdAt.toISOString()})`);
continue;
}
if (hasKeepTags && createdAt <= threeMonthsAgo) {
console.log(`Deleting old package ${pkg.id} with tags: ${tags.join(', ')} (created: ${createdAt.toISOString()}, older than 3 months)`);
} else {
console.log(`Deleting package ${pkg.id} with non-keep tags: ${tags.join(', ')}`);
}
// Delete the package version
try {
await github.rest.packages.deletePackageVersionForUser({
package_type: 'container',
package_name: repo,
username: owner,
package_version_id: pkg.id
});
console.log(`Deleted package ${pkg.id} with tags: ${tags.join(', ')}`);
deletedCount++;
} catch (deleteError) {
console.log(`Failed to delete package ${pkg.id}:`, deleteError.message);
}
}
console.log(`Cleanup completed. Deleted ${deletedCount} package versions.`);
} catch (userError) {
console.log('Error during user-owned package cleanup:', userError.message);
throw userError;
}
} else {
console.log('Error during org-owned package cleanup:', error.message);
throw error;
}
}