hashicorp/nomad · error

volume external ID cannot be updated

Error message

volume external ID cannot be updated

What it means

Also produced by CSIVolume.Merge in nomad/structs/csi.go: an update may not change the volume's ExternalID (the provider-side identifier, e.g. an AWS EBS vol-* ID), because the volume object in Nomad permanently maps to that remote storage object. If the incoming volume has a different non-empty ExternalID than the registered one, Merge appends this error to the multierror.

Source

Thrown at nomad/structs/csi.go:793

// Merge updates the mutable fields of a volume with those from
// another volume. CSIVolume has many user-defined fields which are
// immutable once set, and many fields that are not
// user-settable. Merge will return an error if we try to mutate the
// user-defined immutable fields after they're set, but silently
// ignore fields that are controlled by Nomad.
func (v *CSIVolume) Merge(other *CSIVolume) error {
	if other == nil {
		return nil
	}

	var errs *multierror.Error

	if v.Name != other.Name && other.Name != "" {
		errs = multierror.Append(errs, errors.New("volume name cannot be updated"))
	}
	if v.ExternalID != other.ExternalID && other.ExternalID != "" {
		errs = multierror.Append(errs, errors.New(
			"volume external ID cannot be updated"))
	}
	if v.PluginID != other.PluginID {
		errs = multierror.Append(errs, errors.New(
			"volume plugin ID cannot be updated"))
	}
	if v.CloneID != other.CloneID && other.CloneID != "" {
		errs = multierror.Append(errs, errors.New(
			"volume clone ID cannot be updated"))
	}
	if v.SnapshotID != other.SnapshotID && other.SnapshotID != "" {
		errs = multierror.Append(errs, errors.New(
			"volume snapshot ID cannot be updated"))
	}

	// must be compatible with volume_capabilities
	if v.AccessMode != CSIVolumeAccessModeUnknown ||
		v.AttachmentMode != CSIVolumeAttachmentModeUnknown {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Register the new external ID as a different volume ID rather than mutating the existing registration.
  2. If the old remote volume is gone, deregister the CSI volume in Nomad and register it anew with the new external ID.
  3. Verify the intended external ID with `nomad volume status <id>` and correct your spec to match the existing registration.

Example fix

// before: reusing id ebs-0 with a new provider volume
id = "ebs-0"
external_id = "vol-999"  // was vol-123

// after
id = "ebs-1"
external_id = "vol-999"
// (or deregister ebs-0 and register fresh)
Defensive patterns

Strategy: validation

Validate before calling

existing, _ := client.Volumes().Get(ctx, volID)
if existing != nil && spec.ExternalID != "" && spec.ExternalID != existing.ExternalID {
    return fmt.Errorf("volume %s: external_id is immutable; register a new volume ID", volID)
}

Try / catch

if err := csiVolume.Merge(update); err != nil {
    if strings.Contains(err.Error(), "volume external ID cannot be updated") {
        return fmt.Errorf("provider volume changed: deregister and re-register with the new external_id")
    }
    return err
}

Prevention

When it happens

Trigger: Re-registering/updating a CSI volume ID whose external_id differs from the one originally registered (e.g. pointing an existing volume ID at a newly provisioned cloud disk), or client-side volume reconciliation applying a spec with a changed ExternalID.

Common situations: Terraform/IaC recreating the underlying storage volume (new vol-xxx ID) while Nomad still references the old registration; manually editing the external_id in a volume HCL and re-applying it under the same ID.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/9453bd84a4929c27. Report an issue: GitHub.