kubernetes/kops · error

failed to delete VPC %s (ID %s): %s

Error message

failed to delete VPC %s (ID %s): %s

What it means

deleteVPC wraps the godo VPCsService.Delete failure during DigitalOcean cluster teardown. It fires when the DO API refuses to delete the cluster's VPC (non-empty, permission denied, or transient API error); the VPC name and ID are echoed for correlation.

Source

Thrown at pkg/resources/digitalocean/resources.go:273

	c := cloud.(do.DOCloud)
	dropletID, err := strconv.Atoi(t.ID)
	if err != nil {
		return fmt.Errorf("failed to convert droplet ID to int: %s", err)
	}

	_, err = c.DropletsService().Delete(context.TODO(), dropletID)
	if err != nil {
		return fmt.Errorf("failed to delete droplet: %d, err: %s", dropletID, err)
	}

	return nil
}

func deleteVPC(cloud fi.Cloud, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	_, err := c.VPCsService().Delete(context.TODO(), t.ID)
	if err != nil {
		return fmt.Errorf("failed to delete VPC %s (ID %s): %s", t.Name, t.ID, err)
	}

	return nil
}

func deleteVolume(cloud fi.Cloud, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	volume := t.Obj.(godo.Volume)
	for _, dropletID := range volume.DropletIDs {
		action, resp, err := c.VolumeActionService().DetachByDropletID(context.TODO(), volume.ID, dropletID)
		if err != nil {
			if resp != nil && resp.StatusCode == http.StatusNotFound {
				// Volume is already detached, nothing to do.
				continue
			}
			return fmt.Errorf("failed to detach volume %s: %s", volume.ID, err)
		}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Check the DigitalOcean API token and VPC delete permissions
  2. Ensure no droplets/resources remain in the VPC, then retry
  3. Verify the VPC ID still exists; 404 means already deleted

Example fix

// before
# VPC delete rejected: member droplets still attached
// after
kops delete cluster --cloud digitalocean ...  # removes droplets before VPC; or doctl compute droplet delete <id> first
Defensive patterns

Strategy: retry

Validate before calling

vpcs, _, _ := c.VPCsService().List(context.TODO(), &godo.ListOptions{})
for _, v := range vpcs {
	if v.ID == vpcID && v.Default {
		return errors.New("cannot delete the default VPC")
	}
}

Try / catch

if _, err := c.VPCsService().Delete(context.TODO(), t.ID); err != nil {
	var gerr *godo.ErrorResponse
	if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound {
		return nil // already deleted
	}
	return fmt.Errorf("failed to delete VPC %s (ID %s): %w", t.Name, t.ID, err)
}

Prevention

When it happens

Trigger: VPC delete API returns 404 (already deleted), 409/422 (VPC still in use — resources like droplets/LBs remain attached), 403 (scope), or transient API error.

Common situations: Deleting a VPC that DO does not allow removing because member resources still exist; VPC deleted manually earlier; default VPC cannot be deleted; token scope too narrow.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/c9c6f8cce6d63d18. Report an issue: GitHub.