hashicorp/terraform · error
Cannot set both 'source' and 'content'
Error message
Cannot set both 'source' and 'content'
What it means
Thrown by the file provisioner's ValidateProvisionerConfig (resource_provisioner.go:69). The file provisioner copies a single file/dir from a local 'source' OR writes inline 'content'; both arguments are mutually exclusive. If both source and content are non-null in the provisioner block, validation appends this error and returns immediately, failing the config validation phase before any apply.
Solutions
- Remove either the `source` or the `content` argument from the provisioner block so exactly one is set.
- If you need both behaviors, split into two separate file provisioner blocks.
Example fix
# before
provisioner "file" {
source = "./app.conf"
content = "key=value"
destination = "/etc/app/app.conf"
}
# after
provisioner "file" {
source = "./app.conf"
destination = "/etc/app/app.conf"
} Defensive patterns
Strategy: validation
Validate before calling
// Before applying, validate the file provisioner sets exactly one payload source.
func validateFileProvisioner(src, content string) error {
if src != "" && content != "" {
return errors.New("Cannot set both 'source' and 'content'")
}
return nil
} Type guard
type fileProvisionerArgs struct {
Source optional[string]
Content optional[string]
Destination string
}
func (a fileProvisionerArgs) valid() bool {
return a.Destination != "" && a.Source.set != a.Content.set // exactly one
} Prevention
- Run terraform validate before apply so the provisioner's ValidateProvisionerConfig catches mutual-exclusion errors.
- Never set both source and content in a file provisioner block.
When it happens
Trigger: A provisioner "file" block whose HCL sets both `source = ...` and `content = ...`. The coercion is done via the provisioner schema; cfg.GetAttr("source") and cfg.GetAttr("content") are both non-null, hitting the first switch case.
Common situations: Copy-pasting examples that combine a source path with inline content. Refactoring a file provisioner from source-based to content-based and forgetting to remove the old attribute.
Related errors
- Must provide one of 'source' or 'content'
- host for provisioner cannot be empty
- invalid empty string in 'script'
- invalid empty string in 'scripts'
- invalid null string in 'script'
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/51edd95951ef778b.
Report an issue: GitHub.
Appendix: source
Thrown at internal/builtin/provisioners/file/resource_provisioner.go:69
},
},
}
resp.Provisioner = schema
return resp
}
func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
}
source := cfg.GetAttr("source")
content := cfg.GetAttr("content")
switch {
case !source.IsNull() && !content.IsNull():
resp.Diagnostics = resp.Diagnostics.Append(errors.New("Cannot set both 'source' and 'content'"))
return resp
case source.IsNull() && content.IsNull():
resp.Diagnostics = resp.Diagnostics.Append(errors.New("Must provide one of 'source' or 'content'"))
return resp
}
return resp
}
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
if req.Connection.IsNull() {
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"file provisioner error",
"Missing connection configuration for provisioner.",
))
return resp
}View on GitHub (pinned to d32a084675)