Overview
Remove unused Docker images from an environment to reclaim disk space. Supports pruning dangling images (untagged) or all unused images (including tagged images not used by any container).
Endpoint
POST /api/prune/images?env={environmentId}&dangling={true|false}
Query Parameters
Environment ID to prune images from. Optional for local environments.
Prune mode:
true or omitted: Remove only dangling images (untagged)
false: Remove all unused images including tagged ones
Authentication
Requires images:remove permission for the specified environment.
Returns job ID for progress tracking:
{
"jobId": "550e8400-e29b-41d4-a716-446655440000"
}
The final result contains deleted images and space reclaimed:
{
"success": true,
"result": {
"ImagesDeleted": [
{
"Deleted": "sha256:abc123..."
},
{
"Deleted": "sha256:def456..."
},
{
"Untagged": "nginx:old"
}
],
"SpaceReclaimed": 524288000
}
}
Response Fields
Array of deleted/untagged image objects:
Deleted: Image SHA256 that was removed
Untagged: Image tag that was removed
Total disk space reclaimed in bytes
Prune Modes
Dangling Only (default)
Removes only untagged images (labeled <none>:<none>):
POST /api/prune/images?env=1
# or explicitly
POST /api/prune/images?env=1&dangling=true
This is the safest option and matches Docker CLI default behavior:
All Unused Images
Removes all images not currently used by any container:
POST /api/prune/images?env=1&dangling=false
Equivalent to Docker CLI:
Pruning with dangling=false will remove all unused images including tagged ones. This can remove images you may want to keep for quick container creation.
Implementation
export const POST: RequestHandler = async (event) => {
const { url, cookies } = event;
const auth = await authorize(cookies);
const envId = url.searchParams.get('env');
const envIdNum = envId ? parseInt(envId) : undefined;
const danglingOnly = url.searchParams.get('dangling') !== 'false';
// Permission check with environment context
if (auth.authEnabled && !await auth.can('images', 'remove', envIdNum)) {
return json({ error: 'Permission denied' }, { status: 403 });
}
return createJobResponse(async (send) => {
try {
const result = await pruneImages(danglingOnly, envIdNum);
// Audit log
await audit(event, 'prune', 'image', {
environmentId: envIdNum,
description: `Pruned ${danglingOnly ? 'dangling' : 'unused'} images`,
details: { danglingOnly, result }
});
send('result', { success: true, result });
} catch (error) {
console.error('Error pruning images:', error);
send('result', { success: false, error: 'Failed to prune images' });
}
}, event.request);
};
Docker API Integration
The prune operation uses Docker’s native prune API:
export async function pruneImages(dangling = true, envId?: number | null) {
// dangling=true: only remove untagged images (default Docker behavior)
// dangling=false: remove ALL unused images including tagged ones
// Docker API quirk: to remove all unused, we pass dangling=false filter
const filters = dangling ? '{"dangling":["true"]}' : '{"dangling":["false"]}';
return dockerJsonRequest(
`/images/prune?filters=${encodeURIComponent(filters)}`,
{ method: 'POST' },
envId
);
}
Docker API uses dangling=false to prune all unused images, which is counterintuitive. The filter specifies what to keep, not what to remove.
Usage Examples
Prune Dangling Images
curl -X POST 'https://dockhand.example.com/api/prune/images?env=1' \
-H 'Cookie: session=...'
Prune All Unused Images
curl -X POST 'https://dockhand.example.com/api/prune/images?env=1&dangling=false' \
-H 'Cookie: session=...'
Track Prune Progress
const response = await fetch('/api/prune/images?env=1', {
method: 'POST'
});
const { jobId } = await response.json();
const eventSource = new EventSource(`/api/jobs/${jobId}`);
eventSource.addEventListener('result', (e) => {
const result = JSON.parse(e.data);
if (result.success) {
const spaceGB = (result.result.SpaceReclaimed / 1024 / 1024 / 1024).toFixed(2);
const count = result.result.ImagesDeleted?.length || 0;
console.log(`Pruned ${count} images, reclaimed ${spaceGB} GB`);
} else {
console.error('Prune failed:', result.error);
}
eventSource.close();
});
Automated Cleanup Script
async function cleanupOldImages(environmentId: number) {
// First, check available disk space
const stats = await fetch(`/api/system/stats?env=${environmentId}`);
const { disk } = await stats.json();
// If disk usage is above 80%, prune all unused images
if (disk.usagePercent > 80) {
console.log('Disk usage high, pruning all unused images...');
await fetch(`/api/prune/images?env=${environmentId}&dangling=false`, {
method: 'POST'
});
} else {
// Otherwise, just prune dangling images
await fetch(`/api/prune/images?env=${environmentId}`, {
method: 'POST'
});
}
}
Error Responses
Permission denied - user lacks images:remove permission{ "error": "Permission denied" }
Prune operation failed{
"success": false,
"error": "Failed to prune images"
}
Scheduled Pruning
Dockhand supports automatic image pruning via the scheduler:
// Scheduled task runs periodically to prune dangling images
export async function imagePruneTask() {
const environments = await getEnvironments();
for (const env of environments) {
if (env.autoPrune) {
await pruneImages(true, env.id);
}
}
}
Configure auto-prune in environment settings.
Audit Logging
Prune operations are logged with full details:
await audit(event, 'prune', 'image', {
environmentId: envIdNum,
description: `Pruned ${danglingOnly ? 'dangling' : 'unused'} images`,
details: { danglingOnly, result }
});
Audit logs include:
- Number of images removed
- Space reclaimed
- Prune mode (dangling vs all unused)
- User who initiated the operation
Best Practices
-
Regular Dangling Prune: Run dangling-only prune regularly (daily/weekly) to remove build artifacts
-
Conservative All-Unused Prune: Only use
dangling=false when disk space is critically low
-
Monitor Impact: Check
SpaceReclaimed to track cleanup effectiveness
-
Automated Cleanup: Configure scheduled pruning in environment settings
-
Pre-deployment Prune: Prune before deploying new images to ensure available space
-
Combine with System Prune: For maximum space recovery, use system-wide prune operations
Notes
- Prune operations only affect unused images
- Images referenced by containers (running or stopped) are never pruned
- Dangling images are typically intermediate build layers
- Tagged images are only removed with
dangling=false
- Operations are atomic - either all images are pruned or none
- Space reclaimed includes all image layers no longer referenced
- Audit logs track all prune operations for compliance