The management of cloud infrastructure through declarative programming introduces a fundamental challenge: the reconciliation of the desired state defined in code with the actual state of the live environment. In a standard execution flow, Pulumi compares the current state stored in the stack with the values provided in the Pulumi program to determine if a resource needs to be updated or replaced. However, there are critical scenarios where this automatic synchronization becomes a liability rather than an asset. The ignoreChanges resource option serves as the primary mechanism to decouple specific resource properties from this reconciliation process. By explicitly instructing the Pulumi engine to disregard changes to designated properties, engineers can prevent unintentional resource replacements, accommodate external management systems, and maintain granular control over the lifecycle of complex infrastructure components.
The Mechanics of the ignoreChanges Option
The ignoreChanges resource option specifies a precise list of properties that Pulumi will overlook during the update phase of an existing resource. Rather than utilizing the value currently defined in the Pulumi program to determine if an update or replacement is necessary, Pulumi defaults to using the value stored in the stack state from the previous successful deployment.
This mechanism creates a strategic divergence between the code and the state. Under normal operations, if a property in the program differs from the property in the state, Pulumi triggers an update. When ignoreChanges is applied, this logic is bypassed for the specified keys. The impact of this behavior is that the infrastructure remains stable even if the code is updated to a new value, effectively "freezing" the property at the value it held during its last recorded state.
It is critical to understand that this option does not apply globally to all stages of a resource's life. When a resource is first created, there is no previous value in the state. In this specific instance, Pulumi utilizes the value provided by the program. Once the resource is successfully created and the state is recorded, any subsequent updates to that property in the program will be ignored.
Technical Scope and Application Constraints
The ignoreChanges option is defined within the base resource-options type across every Pulumi SDK. While this means that the code will not fail at compile time if the option is passed to a component resource, the runtime behavior is fundamentally different.
The ignoreChanges option has no direct effect on component resources. Component resources act as logical groupings of other resources; they are not individual cloud entities. Consequently, passing this option to a component does not automatically apply it to the child resources contained within that component. The implementation of the component resource itself decides whether it will honor the option and how it will propagate that logic to its internal resource inputs.
Furthermore, the scope of this option is strictly limited to resource inputs. It cannot be applied to resource outputs. This distinction is vital for architects designing complex dependency graphs, as they must ensure that the properties being ignored are those passed into the resource during configuration, not those emitted by the resource after creation.
Advanced Property Targeting and Nested Paths
Pulumi provides the ability to target specific parts of a resource's input structure rather than just top-level properties. This is achieved through the use of property paths, allowing for a high degree of precision when ignoring changes to nested objects or arrays.
For instance, in a complex AWS Load Balancer configuration, an engineer may want to maintain control over the weights of target groups without triggering a full update of the listener. By using a path such as defaultActions[*].forward.targetGroups[*].weight, the user can target only the weight property within a nested array of target groups.
The handling of arrays within ignoreChanges follows a specific logic to handle varying lengths between the state and the program:
- Elements present in both the old (state) and new (program) arrays are ignored.
- If the new input array is longer than the old array, the additional elements are accepted from the new array.
- If the new input array is shorter than the old array, Pulumi only takes the number of elements present in the new array from the original state.
To illustrate this with specific values:
- If the old array is
[1, 2]and the new array is[a, b, c], the resulting state becomes[1, 2, c]. - If the old array is
[1, 2, 3]and the new array is[a, b], the resulting state becomes[1, 2].
Strategic Use Cases for State Divergence
The application of ignoreChanges is typically driven by the need to integrate Pulumi with external processes or to manually override the declarative nature of the platform.
One prominent example is the management of Amazon Machine Images (AMIs). In a laboratory environment, a user might employ a dynamic AMI lookup. If Pulumi were to track the AMI ID strictly, every update to the lookup logic would trigger a replacement of the bastion hosts. By adding pulumi.IgnoreChanges([]string{"ami"}), the engineer ensures that the bastion hosts are not replaced every time the AMI ID changes in the lookup. This shifts the control of resource replacement from the automated engine to the human operator.
Another critical use case involves properties managed by external systems. Tags are a frequent example. In many enterprise environments, cloud tags are automatically modified by external governance tools, cost-allocation scripts, or security scanners. If Pulumi is configured to manage these tags, it would attempt to revert the external changes back to the values defined in the code during every pulumi up operation. Applying ignore_changes=["tags"] prevents this conflict, allowing external processes to modify tags without triggering a reconciliation loop.
Integration with State Synchronization and Drift
A critical risk associated with the use of ignoreChanges is the potential for stale state. Because Pulumi relies on the last recorded state for ignored properties, it does not automatically read the live value from the cloud provider during a standard update.
If a resource is modified externally—either through intentional management or unintentional drift—and the user subsequently runs a Pulumi update, Pulumi will continue to use the value stored in the state. If that state value is now outdated, Pulumi may send that stale value back to the provider, potentially overwriting the external changes that the user intended to preserve.
To mitigate this, users must synchronize the stack. The following commands are used to pull the latest provider values into the state:
pulumi refreshpulumi up --refresh
Performing a refresh ensures that the "previous state value" used by ignoreChanges is actually the current live value. Without this step, the ignoreChanges logic operates on a snapshot that may no longer reflect reality, leading to unintentional infrastructure modifications.
Comparison of Resource Options
To provide context on where ignoreChanges fits within the broader lifecycle management of Pulumi, it is useful to compare it with other common resource options.
| Option | Primary Function | Impact on Lifecycle |
|---|---|---|
ignoreChanges |
Skips diff on specific properties | Prevents updates/replacements based on specific input changes |
retain_on_delete |
Keeps resource after code removal | Prevents the cloud provider from deleting the physical resource |
protect |
Prevents accidental deletion | Blocks the deletion of the resource until the flag is removed |
Implementation Examples
The following examples demonstrate the application of ignoreChanges across different SDKs and scenarios.
TypeScript Implementation for AWS Load Balancer
In this scenario, the goal is to allow the creation of a listener and target groups but prevent future updates to the weights of those target groups.
```typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const frontEnd = new aws.lb.LoadBalancer("frontEnd", {});
const frontEndBlue = new aws.lb.TargetGroup("frontEndBlue", {});
const frontEndGreen = new aws.lb.TargetGroup("frontEndGreen", {});
const frontEndListener = new aws.lb.Listener("frontEndListener", {
loadBalancerArn: frontEnd.arn,
port: 443,
protocol: "HTTPS",
sslPolicy: "ELBSecurityPolicy-2016-08",
certificateArn: "arn:aws:iam:187416307283:server-certificate/testcertrab3wuqgja25ct3n4jdj2tzu4",
defaultActions: [{
type: "forward",
forward: {
targetGroups: [
{
arn: frontEndBlue.arn,
weight: 100,
},
{
arn: frontEndGreen.arn,
weight: 0,
},
],
},
}],
}, {
ignoreChanges: ['defaultActions[].forward.targetGroups[].weight']
});
```
Python Implementation for EC2 Instance Tags
This example demonstrates how to prevent Pulumi from reverting tags that are managed by an external enterprise process.
```python
import pulumi
import pulumi_aws as aws
server = aws.ec2.Instance("web",
ami="ami-0abcdef1234567890",
instancetype="t3.micro",
opts=pulumi.ResourceOptions(
ignorechanges=["tags"],
),
)
```
Analysis of Requested Enhancements and Future State
Within the Pulumi community, there has been discussion regarding the limitations of the current ignoreChanges implementation, specifically concerning how it interacts with the provider's read operations. A proposed enhancement suggests a feature where users can specify properties that should not be modified from their current deployed state, but with a key difference: instead of reading from the stack state, the system would perform a live read via the provider.
The current logic follows this flow:
1. Resource created -> Value stored in state.
2. Update triggered -> Pulumi compares program value vs. state value.
3. ignoreChanges active -> Pulumi uses state value, ignoring program value.
The proposed "Provider Read" logic would alter this to:
1. Update triggered -> Pulumi performs a live read from the cloud provider.
2. Pulumi overrides the program value with the retrieved live value.
3. Manual modifications made in the cloud console are maintained on subsequent updates.
This would solve the "stale state" problem associated with pulumi refresh, as the provider would become the source of truth for the ignored property in real-time, rather than relying on the serialized state stored in the Pulumi backend.
Operational Analysis and Conclusion
The ignoreChanges option is a critical tool for engineers operating in hybrid management environments. By breaking the strict declarative link between code and infrastructure, it allows for the coexistence of automated IaC (Infrastructure as Code) and manual or external configuration.
The operational consequence of utilizing ignoreChanges is a shift in responsibility. The engineer is no longer relying on the Pulumi engine to ensure the live environment matches the code for those specific properties. This introduces a requirement for disciplined state management. The reliance on the stack state means that any engineer using this option must be aware of the "drift" that occurs between the live environment and the recorded state.
To maintain system integrity, the following operational workflow is recommended:
1. Identify properties subject to external modification (e.g., tags, AMI IDs).
2. Implement ignoreChanges to prevent reconciliation loops.
3. Periodically execute pulumi refresh to ensure the stack state accurately reflects the live environment.
4. When a replacement is actually desired (e.g., upgrading a bastion host AMI), use the Pulumi CLI flags to selectively destroy and replace the specific resource.
In summary, ignoreChanges is not a way to disable Pulumi's tracking, but a way to specify which source of truth—the code or the state—takes precedence during an update. When used correctly, it prevents catastrophic resource replacements and allows Pulumi to scale within complex, multi-tool enterprise ecosystems.