Add Docker hygiene, deployment manifests, and daily log digest
Prevents Docker disk bloat by adding log rotation (10MB max, 3 files) to all container creation and update paths, auto-pruning dangling images after deploy/remove/update, and a daily maintenance module that cleans build cache and warns on disk thresholds. Saves a deployment manifest in services.json at deploy time so users can restore all their apps after a Docker purge. Adds restore-all and restore-single endpoints that recreate containers, Caddy config, and DNS records from the saved manifests. Adds an hourly log collector and daily digest generator that summarizes errors, warnings, and events across all services into a single human-readable report with guidance on where to investigate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -113,7 +113,8 @@ module.exports = function(ctx, helpers) {
|
||||
HostConfig: {
|
||||
PortBindings: {},
|
||||
Binds: processedTemplate.docker.volumes || [],
|
||||
RestartPolicy: { Name: 'unless-stopped' }
|
||||
RestartPolicy: { Name: 'unless-stopped' },
|
||||
LogConfig: DOCKER.LOG_CONFIG
|
||||
},
|
||||
Env: Object.entries(processedTemplate.docker.environment || {}).map(([k, v]) => `${k}=${v}`),
|
||||
Labels: {
|
||||
@@ -152,6 +153,16 @@ module.exports = function(ctx, helpers) {
|
||||
const container = await ctx.docker.client.createContainer(containerConfig);
|
||||
await container.start();
|
||||
|
||||
// Prune dangling images to prevent disk bloat
|
||||
try {
|
||||
const pruneResult = await ctx.docker.client.pruneImages({ filters: { dangling: { true: true } } });
|
||||
if (pruneResult.SpaceReclaimed > 0) {
|
||||
ctx.log.info('docker', 'Pruned dangling images after deploy', { spaceReclaimed: Math.round(pruneResult.SpaceReclaimed / 1024 / 1024) + 'MB' });
|
||||
}
|
||||
} catch (pruneErr) {
|
||||
ctx.log.debug('docker', 'Image prune after deploy failed', { error: pruneErr.message });
|
||||
}
|
||||
|
||||
await ctx.portLockManager.releasePorts(lockId);
|
||||
ctx.log.info('deploy', 'Port locks released', { lockId });
|
||||
return container.id;
|
||||
@@ -294,6 +305,44 @@ module.exports = function(ctx, helpers) {
|
||||
// Build service URL based on routing mode
|
||||
const serviceUrl = ctx.buildServiceUrl(config.subdomain);
|
||||
|
||||
// Build deployment manifest — the full recipe to recreate this container
|
||||
const deploymentManifest = {
|
||||
templateId: appId,
|
||||
config: {
|
||||
subdomain: config.subdomain,
|
||||
port: config.port || template.defaultPort,
|
||||
ip: config.ip,
|
||||
mediaPath: config.mediaPath || undefined,
|
||||
createDns: config.createDns || false,
|
||||
tailscaleOnly: config.tailscaleOnly || false,
|
||||
allowedIPs: config.allowedIPs || [],
|
||||
customVolumes: config.customVolumes || undefined,
|
||||
useExisting: false
|
||||
},
|
||||
container: template.isStaticSite ? null : {
|
||||
image: processedTemplate.docker.image,
|
||||
ports: processedTemplate.docker.ports,
|
||||
volumes: processedTemplate.docker.volumes || [],
|
||||
environment: (() => {
|
||||
// Strip sensitive values from stored env (claim tokens, secrets)
|
||||
const env = { ...processedTemplate.docker.environment };
|
||||
for (const key of Object.keys(env)) {
|
||||
if (/claim|secret|password|token|key/i.test(key) && env[key]) {
|
||||
env[key] = ''; // Clear sensitive values — user re-enters on restore
|
||||
}
|
||||
}
|
||||
return env;
|
||||
})(),
|
||||
capabilities: processedTemplate.docker.capabilities || undefined
|
||||
},
|
||||
caddy: {
|
||||
tailscaleOnly: config.tailscaleOnly || false,
|
||||
allowedIPs: config.allowedIPs || [],
|
||||
subpathSupport: template.subpathSupport || 'strip',
|
||||
routingMode: ctx.siteConfig.routingMode
|
||||
}
|
||||
};
|
||||
|
||||
await ctx.addServiceToConfig({
|
||||
id: config.subdomain, name: template.name,
|
||||
logo: template.logo || `/assets/${appId}.png`,
|
||||
@@ -301,7 +350,8 @@ module.exports = function(ctx, helpers) {
|
||||
containerId, appTemplate: appId,
|
||||
tailscaleOnly: config.tailscaleOnly || false,
|
||||
routingMode: ctx.siteConfig.routingMode,
|
||||
deployedAt: new Date().toISOString()
|
||||
deployedAt: new Date().toISOString(),
|
||||
deploymentManifest
|
||||
});
|
||||
ctx.log.info('deploy', 'Service added to dashboard', { subdomain: config.subdomain });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user