feat: garbage-collect dangling images after rebuilds and deletes
Nixpacks moves an installation's image tag to the newly built image, leaving the previous one dangling. buildInstallation now removes the superseded image (best-effort, skipped if still referenced), and removeInstallationContainer removes an installation's image when the installation itself is deleted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,13 @@ import {
|
|||||||
} from "../db/installations.js";
|
} from "../db/installations.js";
|
||||||
import { cloneOrUpdateRepo, readEnvExampleKeys, repoDir } from "../build/git.js";
|
import { cloneOrUpdateRepo, readEnvExampleKeys, repoDir } from "../build/git.js";
|
||||||
import { runNixpacksBuild } from "../build/nixpacks.js";
|
import { runNixpacksBuild } from "../build/nixpacks.js";
|
||||||
import { runInstallationContainer, stopInstallationContainer, waitForContainerHealth } from "../docker/containers.js";
|
import {
|
||||||
|
runInstallationContainer,
|
||||||
|
stopInstallationContainer,
|
||||||
|
waitForContainerHealth,
|
||||||
|
getImageId,
|
||||||
|
removeImageIfUnused,
|
||||||
|
} from "../docker/containers.js";
|
||||||
import { generateBearerToken } from "../crypto/secrets.js";
|
import { generateBearerToken } from "../crypto/secrets.js";
|
||||||
|
|
||||||
export interface DraftResult {
|
export interface DraftResult {
|
||||||
@@ -58,6 +64,8 @@ export async function buildInstallation(id: string): Promise<Installation> {
|
|||||||
|
|
||||||
updateInstallation(id, { status: "building", statusDetail: null });
|
updateInstallation(id, { status: "building", statusDetail: null });
|
||||||
const imageTag = `mcpmgr/${installation.slug}:latest`;
|
const imageTag = `mcpmgr/${installation.slug}:latest`;
|
||||||
|
const previousImageId = installation.imageTag ? await getImageId(installation.imageTag) : undefined;
|
||||||
|
|
||||||
const result = await runNixpacksBuild(installation.slug, repoDir(installation.slug), imageTag);
|
const result = await runNixpacksBuild(installation.slug, repoDir(installation.slug), imageTag);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -67,6 +75,15 @@ export async function buildInstallation(id: string): Promise<Installation> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nixpacks moves the tag to the new image, leaving the previous one dangling — clean it up
|
||||||
|
// so rebuilds don't accumulate untagged images forever.
|
||||||
|
if (previousImageId) {
|
||||||
|
const newImageId = await getImageId(imageTag);
|
||||||
|
if (newImageId && newImageId !== previousImageId) {
|
||||||
|
await removeImageIfUnused(previousImageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return updateInstallation(id, { status: "built", statusDetail: null, imageTag });
|
return updateInstallation(id, { status: "built", statusDetail: null, imageTag });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,4 +115,9 @@ export async function removeInstallationContainer(id: string): Promise<void> {
|
|||||||
const installation = getInstallationById(id);
|
const installation = getInstallationById(id);
|
||||||
if (!installation) return;
|
if (!installation) return;
|
||||||
await stopInstallationContainer(installation.containerName);
|
await stopInstallationContainer(installation.containerName);
|
||||||
|
|
||||||
|
if (installation.imageTag) {
|
||||||
|
const imageId = await getImageId(installation.imageTag);
|
||||||
|
if (imageId) await removeImageIfUnused(imageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user