Why every script needs a dry-run flag
Practical perspective from an IT leader working across operations, security, automation, and change.
5 minute read with practical, decision-oriented guidance.
Leaders and operators looking for concise, actionable takeaways.
Topics covered
There is a post trending on Hacker News today about the --dry-run flag. Henrik Warne writes about adding it to a reporting application early in development and being surprised by how useful it became. I have been nodding along because this matches my experience exactly.
A --dry-run option lets you inspect intended changes before executing them. If you have ever run rsync --dry-run before committing to a massive file sync, or used terraform plan before terraform apply, you already know the value.
What dry-run actually means
A dry-run flag tells your script to show what it would do without actually doing it. Print the files that would be deleted. Log the API calls that would be made. Display the database rows that would be updated. Then exit without changing anything.
The preview should show intended changes without executing them.
When a colleague asks "what will this script do?", you should be able to run it with --dry-run and show them. The preview should leave no changes to clean up.
Where this matters most
Database migrations
Before running a migration that modifies production data, a dry-run should output:
- How many rows will be affected
- Sample of the changes (first 10 rows, perhaps)
- Any constraints that might fail
- Estimated execution time
File operations
Scripts that move, rename, or delete files should preview the operations. I once watched a junior engineer accidentally delete a week of customer uploads because a cleanup script had no preview mode. That script has a --dry-run flag now.
API integrations
For external actions such as sending email, posting to Slack or updating CRM records, a dry-run should log the proposed action without executing it. This is invaluable for testing integrations without spamming real systems.
Infrastructure changes
Terraform popularised plan before apply. Ansible has --check mode. Kubernetes has --dry-run=client. These tools understood that showing the diff before making changes reduces incidents significantly.
Implementation patterns
The simplest approach is a global flag that gates all side effects:
def delete_old_files(directory, dry_run=False):
files = find_files_older_than(directory, days=30)
for file in files:
if dry_run:
print(f"Would delete: {file}")
else:
os.remove(file)
print(f"Deleted: {file}")
print(f"Total: {len(files)} files {'would be' if dry_run else ''} deleted")For more complex scripts, consider a transaction-style approach where you collect all intended actions, display them, then execute only if not in dry-run mode:
class ActionPlan:
def __init__(self):
self.actions = []
def add(self, description, execute_fn):
self.actions.append((description, execute_fn))
def preview(self):
for desc, _ in self.actions:
print(f" - {desc}")
def execute(self, dry_run=False):
if dry_run:
print("Dry run - the following actions would be taken:")
self.preview()
return
for desc, fn in self.actions:
print(f"Executing: {desc}")
fn()The hidden benefit: better logging
Adding dry-run support forces you to think about what your script actually does. You cannot preview an action without first describing it clearly. This naturally improves your logging, error messages, and overall observability.
Scripts with good dry-run output tend to have good production logging too. The same descriptions you write for preview mode become your audit trail.
Common objections
"IT adds complexity"
Yes, but minimal complexity. A single boolean flag and some conditional prints. Previewing changes is easier to review than diagnosing an unexpected destructive action.
"Our scripts are simple enough"
Adding preview support early lets you test it as the script grows. Retrofitting it after an incident may miss existing side effects.
"We have staging environments"
Staging helps, but it is not the same as previewing against production data. A dry-run against your actual database shows you what will really happen, not what would happen to synthetic test data.
Making it the default
I have started making --dry-run the default for destructive scripts. You have to explicitly pass --execute or --no-dry-run to make changes. This requires an explicit execution flag before the script changes data.
$ ./cleanup-old-data.py
$ ./cleanup-old-data.py --executeThis is particularly valuable for scripts that run via cron or automation. A misconfigured job that runs in dry-run mode by default produces logs instead of damage.
Use previews in routine work
Henrik Warne added --dry-run on a whim and found himself using it daily. That matches my experience. It can help before deployments, during debugging and incident response, and when demonstrating changes to stakeholders.
The pattern is old. Subversion had it. rsync has had it for decades. But it remains underused in custom scripts and internal tools. Automation that changes state should provide a way to preview those changes.
Test that the preview path leaves data unchanged before relying on it.
If you are building automation that touches production systems, dry-run is just one layer of defence. Pair it with proper governance controls and a solid engineering practice to keep technical debt from creeping in. For hands-on support with engineering practices, automation architecture, or infrastructure design, my technical consulting practice can help.
Inspired by Henrik Warne's post which is worth reading in full.
Share this post
About the author
Daniel J Glover
IT Leader with experience spanning IT management, compliance, development, automation, AI, and project management. I write about technology, leadership, and building better systems.
Continue exploring
Keep building context around this topic
Jump to closely related posts and topic hubs to deepen understanding and discover connected ideas faster.
Explore topic hubs
Related article
Google Workspace automation: controls
Google Workspace automation is gaining integration options. Decide permissions, approval boundaries and failure handling before enabling Workspace Studio tools.
Related article
Apps Script data residency: scope checks
Review Apps Script data residency after Google's data regions announcement. Map what your automation stores, processes and sends to external services.
Related article
Docker Compose self-hosted services guide
Choose self-hosted Docker Compose services, understand network and secret boundaries, and build a local BookStack example with a recovery plan.
Related article
Automated security scanning guide
A practical guide to building an automated vulnerability scanning pipeline when you have a small team and limited budget for enterprise security tooling.
Ready to improve your IT operations?
Request a free 30-minute consultation to discuss your IT challenges. Send a short outline and I will reply to arrange a suitable time. No obligation.
Request a free 30-minute consultationGet Occasional IT Leadership Insights
IT leadership insights, occasionally. No fluff. Unsubscribe any time.
No spam. Unsubscribe any time.