mirror of
https://github.com/trailofbits/claude-code-devcontainer
synced 2026-06-21 14:11:48 +00:00
feat: add devc destroy command and standardize volume naming (#31)
Add `devc destroy [-f]` to cleanly remove all Docker resources
(container, volumes, images) for a project. Includes resource
discovery via container inspection, itemized pre-deletion summary,
y/N confirmation prompt with --force bypass, running container
warning, and idempotent no-op when no resources exist.
Rename volume prefix from claude-code-* to devc-* and include
workspace folder name for self-identifying volumes
(e.g., devc-myproject-config-{devcontainerId}).
Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com>
This commit is contained in:
@@ -120,6 +120,7 @@ claude # Ready to work
|
||||
devc . Install template + start container in current directory
|
||||
devc up Start the devcontainer
|
||||
devc rebuild Rebuild container (preserves persistent volumes)
|
||||
devc destroy [-f] Remove container, volumes, and image for current project
|
||||
devc down Stop the container
|
||||
devc shell Open zsh shell in container
|
||||
devc exec CMD Execute command inside the container
|
||||
@@ -130,6 +131,8 @@ devc template DIR Copy devcontainer files to directory
|
||||
devc self-install Install devc to ~/.local/bin
|
||||
```
|
||||
|
||||
> **Note:** Use `devc destroy` to clean up a project's Docker resources. Removing containers manually (e.g., `docker rm`) will leave orphaned volumes and images behind that `devc destroy` won't be able to find.
|
||||
|
||||
## Session Sync for `/insights`
|
||||
|
||||
Claude Code's `/insights` command analyzes your session history, but it only reads from `~/.claude/projects/` on the host. Sessions inside devcontainer volumes are invisible to it.
|
||||
|
||||
+3
-3
@@ -42,9 +42,9 @@
|
||||
},
|
||||
"remoteUser": "vscode",
|
||||
"mounts": [
|
||||
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
|
||||
"source=claude-code-config-${devcontainerId},target=/home/vscode/.claude,type=volume",
|
||||
"source=claude-code-gh-${devcontainerId},target=/home/vscode/.config/gh,type=volume",
|
||||
"source=devc-${localWorkspaceFolderBasename}-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
|
||||
"source=devc-${localWorkspaceFolderBasename}-config-${devcontainerId},target=/home/vscode/.claude,type=volume",
|
||||
"source=devc-${localWorkspaceFolderBasename}-gh-${devcontainerId},target=/home/vscode/.config/gh,type=volume",
|
||||
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly",
|
||||
"source=${localWorkspaceFolder}/.devcontainer,target=/workspace/.devcontainer,type=bind,readonly"
|
||||
],
|
||||
|
||||
+155
@@ -39,6 +39,7 @@ Commands:
|
||||
mount <host> <cont> Add a mount to the devcontainer (recreates container)
|
||||
sync [project] [--trusted] Sync sessions from devcontainers to host
|
||||
cp <cont> <host> Copy files/directories from container to host
|
||||
destroy [-f] Remove container, volumes, and image for current project
|
||||
help Show this help message
|
||||
|
||||
Examples:
|
||||
@@ -54,6 +55,8 @@ Examples:
|
||||
devc sync # Sync sessions from all devcontainers
|
||||
devc sync crypto # Sync only matching devcontainer
|
||||
devc cp /some/file ./out # Copy a path from container to host
|
||||
devc destroy # Remove all project Docker resources
|
||||
devc destroy -f # Skip confirmation prompt
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -639,6 +642,155 @@ cmd_dot() {
|
||||
cmd_up "."
|
||||
}
|
||||
|
||||
# Discovers all Docker resources associated with the current workspace.
|
||||
# Sets global variables: CONTAINER_ID, CONTAINER_STATUS, VOLUMES (array), IMAGE, IMAGE_UID
|
||||
discover_resources() {
|
||||
local workspace_folder="$1"
|
||||
local label="devcontainer.local_folder=$workspace_folder"
|
||||
|
||||
CONTAINER_ID=""
|
||||
CONTAINER_STATUS=""
|
||||
VOLUMES=()
|
||||
IMAGE=""
|
||||
IMAGE_UID=""
|
||||
|
||||
# Find container (any state: running, stopped, created, etc.)
|
||||
CONTAINER_ID=$(docker ps -aq --filter "label=$label" 2>/dev/null | head -1)
|
||||
|
||||
if [[ -z "$CONTAINER_ID" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Get container status
|
||||
CONTAINER_STATUS=$(docker inspect "$CONTAINER_ID" --format '{{.State.Status}}' 2>/dev/null || true)
|
||||
|
||||
# Get volumes (docker volumes only, not bind mounts)
|
||||
while IFS= read -r vol; do
|
||||
[[ -n "$vol" ]] && VOLUMES+=("$vol")
|
||||
done < <(docker inspect "$CONTAINER_ID" --format '{{json .Mounts}}' 2>/dev/null \
|
||||
| jq -r '.[] | select(.Type == "volume") | .Name' 2>/dev/null)
|
||||
|
||||
# Get image and its -uid variant
|
||||
IMAGE=$(docker inspect "$CONTAINER_ID" --format '{{.Config.Image}}' 2>/dev/null || true)
|
||||
if [[ -n "$IMAGE" ]]; then
|
||||
if [[ "$IMAGE" == *-uid ]]; then
|
||||
IMAGE_UID="$IMAGE"
|
||||
IMAGE="${IMAGE%-uid}"
|
||||
else
|
||||
IMAGE_UID="${IMAGE}-uid"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
print_destroy_summary() {
|
||||
echo ""
|
||||
log_warn "The following resources will be permanently removed:"
|
||||
echo ""
|
||||
|
||||
if [[ -n "$CONTAINER_ID" ]]; then
|
||||
local container_name
|
||||
container_name=$(docker inspect "$CONTAINER_ID" --format '{{.Name}}' 2>/dev/null | sed 's|^/||')
|
||||
echo " Container: ${container_name:-$CONTAINER_ID}"
|
||||
if [[ "$CONTAINER_STATUS" == "running" ]]; then
|
||||
echo " (currently running -- will be force-stopped)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ${#VOLUMES[@]} -gt 0 ]]; then
|
||||
echo " Volumes:"
|
||||
for vol in "${VOLUMES[@]}"; do
|
||||
echo " $vol"
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -n "$IMAGE" ]]; then
|
||||
echo " Image: $IMAGE"
|
||||
if docker image inspect "$IMAGE_UID" &>/dev/null; then
|
||||
echo " $IMAGE_UID"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_destroy() {
|
||||
local force=false
|
||||
|
||||
# Parse flags
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-f|--force)
|
||||
force=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
local workspace_folder
|
||||
workspace_folder="$(get_workspace_folder "${1:-}")"
|
||||
|
||||
discover_resources "$workspace_folder"
|
||||
|
||||
# No resources found (idempotent behavior)
|
||||
if [[ -z "$CONTAINER_ID" ]]; then
|
||||
log_info "No devcontainer found for $workspace_folder"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_destroy_summary
|
||||
|
||||
# Running container warning
|
||||
if [[ "$CONTAINER_STATUS" == "running" && "$force" != true ]]; then
|
||||
log_warn "Container is currently running!"
|
||||
read -p "Force-stop the running container? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "Aborted."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Main confirmation prompt
|
||||
if [[ "$force" != true ]]; then
|
||||
read -p "Destroy these resources? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "Aborted."
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Deletion, in order: stop, remove container, volumes, images
|
||||
if [[ -n "$CONTAINER_ID" && "$CONTAINER_STATUS" == "running" ]]; then
|
||||
log_info "Stopping container..."
|
||||
docker stop "$CONTAINER_ID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ -n "$CONTAINER_ID" ]]; then
|
||||
log_info "Removing container..."
|
||||
docker rm -f "$CONTAINER_ID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
for vol in "${VOLUMES[@]}"; do
|
||||
log_info "Removing volume: $vol"
|
||||
docker volume rm -f "$vol" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
if [[ -n "$IMAGE" ]]; then
|
||||
log_info "Removing image: $IMAGE"
|
||||
docker rmi -f "$IMAGE" >/dev/null 2>&1 || true
|
||||
if docker image inspect "$IMAGE_UID" &>/dev/null 2>&1; then
|
||||
log_info "Removing image: $IMAGE_UID"
|
||||
docker rmi -f "$IMAGE_UID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
log_success "All resources destroyed for $workspace_folder"
|
||||
}
|
||||
|
||||
# Main command dispatcher
|
||||
main() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
@@ -662,6 +814,9 @@ main() {
|
||||
down)
|
||||
cmd_down "$@"
|
||||
;;
|
||||
destroy)
|
||||
cmd_destroy "$@"
|
||||
;;
|
||||
shell)
|
||||
cmd_shell
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user