Tutorial 12: Use Refresh-Only Mode
Learning Objectives
- Understand refresh-only mode and its benefits over traditional refresh
- Learn when and how to use refresh-only operations safely
- Practice updating state without modifying infrastructure
- Implement refresh-only workflows for state reconciliation
- Handle complex refresh scenarios and edge cases
What is Refresh-Only Mode?
Refresh-only mode is a safer alternative to the traditional terraform refresh
command. It allows you to update your Terraform state to match the real infrastructure without applying any configuration changes.
Traditional Refresh vs Refresh-Only Mode
Traditional Refresh (Deprecated)
# Old method - directly modifies state
terraform refresh
# Problems:
# - Modifies state immediately without review
# - No plan preview of state changes
# - Can cause unexpected side effects
# - No rollback capability
Refresh-Only Mode (Recommended)
# New method - safe state updates
terraform plan -refresh-only # Preview state changes
terraform apply -refresh-only # Apply state updates only
# Benefits:
# - Shows what will change before applying
# - No infrastructure modifications
# - Can be reviewed and approved
# - Safer for production environments
When to Use Refresh-Only Mode
- Drift Detection: Check if infrastructure matches state
- State Reconciliation: Update state after manual changes
- Import Follow-up: Refresh state after importing resources
- Regular Maintenance: Periodic state cleanup
- Troubleshooting: Resolve state inconsistencies
Basic Refresh-Only Operations
Simple Refresh-Only Workflow
Step 1: Preview State Changes
# Check what state changes would occur
terraform plan -refresh-only
Example output:
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# aws_instance.web has changed
~ resource "aws_instance" "web" {
id = "i-1234567890abcdef0"
~ instance_type = "t2.micro" -> "t2.small"
# (29 unchanged attributes hidden)
}
This is a refresh-only plan, so Terraform will not take any actions to undo these.
If you were expecting these changes then you can apply this plan to record the
updated values in the Terraform state without changing any remote objects.
Step 2: Apply State Updates
# Apply the state changes
terraform apply -refresh-only
Example output:
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee that exactly these actions will be performed if you run this command again.
Using Plan Files for Safety
# Create a refresh-only plan file
terraform plan -refresh-only -out=refresh.plan
# Review the plan
terraform show refresh.plan
# Apply the specific plan
terraform apply refresh.plan
Practical Refresh-Only Scenarios
Scenario 1: Instance Type Changed Manually
Setup
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
tags = {
Name = "refresh-example"
}
}
output "instance_id" {
value = aws_instance.web.id
}
output "current_instance_type" {
value = aws_instance.web.instance_type
}
Create and Modify Infrastructure
# Deploy infrastructure
terraform init
terraform apply
# Simulate manual change (outside Terraform)
INSTANCE_ID=$(terraform output -raw instance_id)
aws ec2 modify-instance-attribute \
--instance-id $INSTANCE_ID \
--instance-type '{"Value": "t2.small"}'
Use Refresh-Only to Update State
# Check current state vs reality
terraform plan -refresh-only
Output shows state will be updated:
# aws_instance.web has changed
~ resource "aws_instance" "web" {
id = "i-1234567890abcdef0"
~ instance_type = "t2.micro" -> "t2.small"
# (other attributes unchanged)
}
# Update state to match reality
terraform apply -refresh-only
# Verify state is updated
terraform output current_instance_type
# Should now show "t2.small"
Decision Point After Refresh
Now you have two options:
Option 1: Update configuration to match reality
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.small" # Update to match current state
tags = {
Name = "refresh-example"
}
}
Option 2: Revert infrastructure to match configuration
terraform plan # Shows it will change back to t2.micro
terraform apply # Reverts instance type
Scenario 2: Tags Added Outside Terraform
Simulate Tag Changes
# Add tags manually
aws ec2 create-tags \
--resources $INSTANCE_ID \
--tags Key=Environment,Value=production Key=Team,Value=DevOps
Refresh State and Handle Tags
# Check state changes needed
terraform plan -refresh-only
Output shows new tags:
# aws_instance.web has changed
~ resource "aws_instance" "web" {
id = "i-1234567890abcdef0"
~ tags = {
+ "Environment" = "production"
+ "Team" = "DevOps"
"Name" = "refresh-example"
}
# (other attributes unchanged)
}
# Update state with new tags
terraform apply -refresh-only
Handle Tag Drift
Option 1: Add tags to Terraform configuration
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
tags = {
Name = "refresh-example"
Environment = "production"
Team = "DevOps"
}
}
Option 2: Ignore external tag changes
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
tags = {
Name = "refresh-example"
}
lifecycle {
ignore_changes = [
tags["Environment"],
tags["Team"],
]
}
}
Scenario 3: Resource Deleted and Recreated
Simulate Resource Recreation
# Get current instance details
ORIGINAL_ID=$(terraform output -raw instance_id)
# Terminate instance
aws ec2 terminate-instances --instance-ids $ORIGINAL_ID
# Wait for termination
aws ec2 wait instance-terminated --instance-ids $ORIGINAL_ID
# Create new instance manually with same name
NEW_INSTANCE_ID=$(aws ec2 run-instances \
--image-id $(aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" --query 'Images[0].ImageId' --output text) \
--instance-type t2.micro \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=refresh-example}]' \
--query 'Instances[0].InstanceId' \
--output text)
echo "New instance ID: $NEW_INSTANCE_ID"
Refresh-Only Detection
terraform plan -refresh-only
Output shows resource needs to be recreated:
# aws_instance.web has changed
~ resource "aws_instance" "web" {
~ id = "i-1234567890abcdef0" -> "i-0987654321fedcba0"
~ private_ip = "10.0.1.100" -> "10.0.1.150"
~ public_ip = "54.123.45.67" -> "54.98.76.54"
# (other attributes may have changed)
}
# Update state with new instance details
terraform apply -refresh-only
Advanced Refresh-Only Usage
Targeted Refresh-Only
# Refresh only specific resources
terraform plan -refresh-only -target=aws_instance.web
# Refresh multiple specific resources
terraform plan -refresh-only \
-target=aws_instance.web \
-target=aws_security_group.web
Refresh-Only with Variables
# Use different variable values during refresh
terraform plan -refresh-only \
-var="environment=production" \
-var-file="production.tfvars"
Refresh-Only in Different Workspaces
# Switch to production workspace
terraform workspace select production
# Refresh production state
terraform plan -refresh-only
terraform apply -refresh-only
# Switch back to development
terraform workspace select development
Refresh-Only in Automation
CI/CD Pipeline Integration
# .github/workflows/state-refresh.yml
name: State Refresh Check
on:
schedule:
- cron: '0 8 * * *' # Daily at 8 AM
workflow_dispatch:
jobs:
check-state-drift:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2
- name: Initialize Terraform
run: terraform init
- name: Check for state drift
run: |
if terraform plan -refresh-only -detailed-exitcode; then
echo "✅ No state drift detected"
else
exitcode=$?
if [ $exitcode -eq 2 ]; then
echo "⚠️ State drift detected!"
echo "Creating refresh plan..."
terraform plan -refresh-only -out=refresh.plan
# Upload plan for review
echo "Please review and apply refresh.plan if appropriate"
# Optional: Auto-apply refresh if safe
# terraform apply refresh.plan
else
echo "❌ Error occurred during refresh check"
exit 1
fi
fi
Automated Refresh Script
#!/bin/bash
# automated-refresh.sh
set -e
ENVIRONMENT=${1:-dev}
AUTO_APPLY=${2:-false}
echo "Checking state drift for environment: $ENVIRONMENT"
# Switch to appropriate workspace
terraform workspace select "$ENVIRONMENT"
# Initialize if needed
terraform init
# Check for drift
echo "Checking for state drift..."
if terraform plan -refresh-only -detailed-exitcode -out=refresh-$ENVIRONMENT.plan; then
echo "✅ No state drift detected in $ENVIRONMENT"
rm -f refresh-$ENVIRONMENT.plan
else
exitcode=$?
if [ $exitcode -eq 2 ]; then
echo "⚠️ State drift detected in $ENVIRONMENT"
# Show what would change
echo "State changes that would be applied:"
terraform show refresh-$ENVIRONMENT.plan
if [ "$AUTO_APPLY" = "true" ]; then
echo "Auto-applying refresh changes..."
terraform apply refresh-$ENVIRONMENT.plan
echo "✅ State refreshed successfully"
else
echo "Run 'terraform apply refresh-$ENVIRONMENT.plan' to update state"
fi
else
echo "❌ Error occurred during refresh check"
exit 1
fi
fi
Usage:
# Check dev environment (manual approval)
./automated-refresh.sh dev
# Check production environment with auto-apply
./automated-refresh.sh prod true
Refresh-Only Best Practices
1. Always Preview First
# Never apply refresh without reviewing
# Bad:
terraform apply -refresh-only -auto-approve
# Good:
terraform plan -refresh-only
# Review output carefully
terraform apply -refresh-only
2. Use Plan Files for Important Changes
# For production or critical changes
terraform plan -refresh-only -out=prod-refresh.plan
# Have team review plan
terraform show prod-refresh.plan
# Apply after approval
terraform apply prod-refresh.plan
3. Document Refresh Decisions
#!/bin/bash
# Document why refresh was needed
echo "State refresh performed on $(date)" >> refresh.log
echo "Reason: Manual instance type change for performance" >> refresh.log
echo "Resources affected: aws_instance.web" >> refresh.log
echo "Applied by: $(whoami)" >> refresh.log
echo "---" >> refresh.log
terraform plan -refresh-only -out=refresh.plan
terraform apply refresh.plan
4. Combine with Configuration Updates
# Workflow for handling drift
# 1. Detect drift
terraform plan -refresh-only
# 2. Update state
terraform apply -refresh-only
# 3. Decide on configuration
# Either update config to match state or plan to revert
# 4. Apply final configuration
terraform plan
terraform apply
5. Monitor Refresh Frequency
# Track when refreshes are needed
echo "$(date): Refresh-only operation performed" >> /var/log/terraform-refresh.log
# Alert if too frequent
RECENT_REFRESHES=$(grep "$(date +%Y-%m-%d)" /var/log/terraform-refresh.log | wc -l)
if [ "$RECENT_REFRESHES" -gt 3 ]; then
echo "⚠️ Warning: Multiple refresh operations today - investigate drift causes"
fi
Troubleshooting Refresh-Only Issues
Issue 1: Refresh Shows No Changes But Drift Exists
# Problem: State appears current but infrastructure differs
# Solution: Check if resources are filtered or targeted
# Verify all resources are being checked
terraform plan -refresh-only
# Check for ignore_changes blocks
grep -r "ignore_changes" *.tf
# Verify resource exists
terraform state list
Issue 2: Permission Errors During Refresh
# Problem: Cannot read resource attributes
# Solution: Check read permissions
# Test AWS permissions
aws ec2 describe-instances --dry-run
# Check specific resource permissions
aws iam simulate-principal-policy \
--policy-source-arn $(aws sts get-caller-identity --query Arn --output text) \
--action-names ec2:DescribeInstances \
--resource-arns "*"
Issue 3: Large State File Refresh Performance
# Problem: Refresh takes too long with many resources
# Solution: Use targeted refresh
# Refresh specific resource types
terraform plan -refresh-only -target=aws_instance
# Or refresh by module
terraform plan -refresh-only -target=module.web_servers
Issue 4: Inconsistent Refresh Results
# Problem: Different results on repeated refresh
# Solution: Check for eventual consistency issues
# Wait between operations
sleep 30
terraform plan -refresh-only
# Check for resource creation in progress
aws ec2 describe-instances --instance-ids $INSTANCE_ID \
--query 'Reservations[0].Instances[0].State.Name'
Refresh-Only vs Other State Operations
Comparison Matrix
Operation | Modifies State | Modifies Infrastructure | Preview Available | Rollback Possible |
---|---|---|---|---|
terraform refresh | ✅ | ❌ | ❌ | ❌ |
terraform plan -refresh-only | ❌ | ❌ | ✅ | N/A |
terraform apply -refresh-only | ✅ | ❌ | ✅ | ❌ |
terraform apply | ✅ | ✅ | ✅ | ⚠️ |
When to Use Each
Use refresh-only when:
- You want to update state to match reality
- No infrastructure changes are needed
- You're investigating drift
- Regular state maintenance
Use regular apply when:
- You want to enforce configuration
- Infrastructure needs to be modified
- Reverting manual changes
Use plan only when:
- Just checking for differences
- Regular monitoring
- Before making decisions
Key Takeaways
- Refresh-only mode is safer than traditional refresh
- Always preview refresh changes with
plan -refresh-only
- Use plan files for important refresh operations
- Refresh-only updates state without changing infrastructure
- Combine refresh-only with configuration decisions
- Monitor refresh frequency to identify drift patterns
- Use targeted refresh for large infrastructures
- Document refresh operations and decisions
- Integrate refresh checks into CI/CD pipelines
Next Steps
- Complete Tutorial 13: Manage Resource Targeting
- Learn about workspace management strategies
- Explore advanced state management techniques
- Practice with complex multi-resource refresh scenarios