Build a DevOps CLI with argparse

Create a DevOps CLI with argparse — Python for DevOps automation tutorial, lesson 32.

Focus: create a devops cli with argparse

Sponsored

You've just SSH'd into a box at 2 AM, and the runbook tells you to grep a log file, restart a service, and tail the output — but you're tired of memorizing the exact flags every time. That's the pain: DevOps life is a stream of repetitive, error-prone shell commands. A custom CLI fixes that by turning your operational knowledge into a single, documented, repeatable tool. In this lesson, you'll create a DevOps CLI with argparse — Python's built-in command-line parser — so your teammates (and future you) can run deploy --env prod --dry-run instead of a fragile five-step process.

The problem this lesson solves

Every day as a DevOps engineer, you execute the same sequences: check health endpoints, tail logs, restart services, roll back deployments. Doing this with raw shell commands leads to three problems:

  • Memory overload — you can't remember if it's --namespace or -n for every tool.
  • Human error — one wrong flag and you've restarted production instead of staging.
  • No guardrails — no validation, no --dry-run, no help text.

A well-built CLI gives you:

  • Consistency — one command for a multi-step process.
  • Safety — required arguments, type checking, and dry-run modes.
  • Discoverability--help shows exactly what the tool does.

This lesson shows you how to create a DevOps CLI with argparse that you'll actually use in your daily automation.

Core concept / mental model

argparse is Python's built-in module for creating user-friendly command-line interfaces. Think of it as the front desk of your script: it greets the user, takes their request (arguments), validates it against a list of rules you define, and hands the validated data to your backend functions.

A CLI has three layers:

  1. Parser — reads sys.argv and defines what arguments are allowed.
  2. Validation — argparse enforces required vs optional, types, choices, and counters.
  3. Action — your Python function that performs the DevOps task using the parsed arguments.

Here's the mental model in a single image (in words):

User input (--env prod --dry-run)  →  argparse parser (rules)  →  Namespace object  →  your function

The Namespace is a simple object with attributes named after your arguments. That's it — clean, predictable, and testable.

How it works step by step

Creating a CLI with argparse follows a repeatable recipe. Master these five steps and you can build a CLI for any operation.

Step 1: Create the parser

Instantiate ArgumentParser with a description — this becomes the help text.

Step 2: Add arguments

For every piece of input the user needs, add an argument. Decide:

  • Positional (e.g., service_name) — required, no flag.
  • Optional (e.g., --env) — flag, may have a default.
  • Typeint, str, float, or even a custom function.
  • Choices — restrict to a list of allowed values (e.g., ['dev', 'staging', 'prod']).
  • Actionstore_true for flags like --verbose or --dry-run.
  • Count — for counting occurrences like -v for verbosity.

Step 3: Parse arguments

Call parser.parse_args() which reads sys.argv, validates, and returns a Namespace.

Step 4: Use the arguments

Pass the namespace (or its attributes) to your business logic functions.

Step 5: Handle errors gracefully

argparse automatically prints usage and exits with code 2 when validation fails. For extra polish, catch SystemExit in tests.

Hands-on walkthrough

Let's build a real-world svcctl CLI — a service controller that checks health, restarts, and rolls back a deployment. It uses the concepts above in a practical way.

Example 1: Minimal health-check CLI

import argparse
import json

def check_health(service: str, env: str) -> dict:
    # Simulated health check
    return {"service": service, "env": env, "status": "OK"}

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Check service health")
    parser.add_argument("service", help="Service name (e.g., web-api)")
    parser.add_argument("--env", choices=["dev", "staging", "prod"], default="dev",
                        help="Environment to check")
    args = parser.parse_args()

    result = check_health(args.service, args.env)
    print(json.dumps(result, indent=2))

Run it:

$ python healthcheck.py web-api --env prod
{
  "service": "web-api",
  "env": "prod",
  "status": "OK"
}

Example 2: Add --verbosity with count and --dry-run

import argparse
import logging

def restart_service(service: str, env: str, dry_run: bool) -> None:
    if dry_run:
        logging.info("[DRY RUN] Would restart %s in %s", service, env)
        return
    # Real restart logic
    logging.info("Restarting %s in %s...", service, env)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Restart a service")
    parser.add_argument("service")
    parser.add_argument("--env", choices=["dev", "staging", "prod"], default="dev")
    parser.add_argument("-v", "--verbose", action="count", default=0,
                        help="Increase verbosity (use -vv for debug)")
    parser.add_argument("--dry-run", action="store_true",
                        help="Show what would happen without doing it")
    args = parser.parse_args()

    if args.verbose >= 2:
        logging.basicConfig(level=logging.DEBUG)
    elif args.verbose == 1:
        logging.basicConfig(level=logging.INFO)
    else:
        logging.basicConfig(level=logging.WARNING)

    restart_service(args.service, args.env, args.dry_run)

Output:

$ python restart.py web-api --env prod --dry-run -vv
DEBUG:root:[DRY RUN] Would restart web-api in prod
INFO:root:[DRY RUN] Would restart web-api in prod

Example 3: Deploy command with --version and subcommands

Many DevOps tools (like kubectl) use subcommands. Here's a simple version:

import argparse

def deploy(args):
    print(f"Deploying {args.app} to {args.env} (dry_run={args.dry_run})")

def rollback(args):
    print(f"Rolling back {args.app} to version {args.version}")

def main():
    parser = argparse.ArgumentParser(description="Deployment CLI")
    subparsers = parser.add_subparsers(dest="command", required=True)

    deploy_parser = subparsers.add_parser("deploy", help="Deploy an app")
    deploy_parser.add_argument("app")
    deploy_parser.add_argument("--env", default="dev")
    deploy_parser.add_argument("--dry-run", action="store_true")
    deploy_parser.set_defaults(func=deploy)

    rollback_parser = subparsers.add_parser("rollback", help="Rollback an app")
    rollback_parser.add_argument("app")
    rollback_parser.add_argument("--version", required=True)
    rollback_parser.set_defaults(func=rollback)

    args = parser.parse_args()
    args.func(args)

if __name__ == "__main__":
    main()

Usage:

$ python deploy.py deploy web-api --env prod --dry-run
Deploying web-api to prod (dry_run=True)
$ python deploy.py rollback web-api --version 1.2.3
Rolling back web-api to version 1.2.3

Example 4: Reading config from a JSON file (bonus)

import argparse
import json

def load_config(path):
    with open(path) as f:
        return json.load(f)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--config", required=True, help="Path to config JSON")
    args = parser.parse_args()
    config = load_config(args.config)
    print(f"Loaded config: {config}")

Compare options / when to choose what

argparse is not the only game in town. Here's how it stacks up:

Tool Pros Cons Best for
argparse Built-in, no deps, stable, powerful Verbose for complex UIs Standard CLI tools, learning, enterprise scripting
Click Decorator-based, chainable, auto help Extra dependency Interactive CLI tools with commands and groups
Typer Type hints, auto completion, modern Requires Python 3.6+, extra deps Fast API-style CLIs, modern codebases
Fire Zero-config, turns any function into CLI Less control, magic behavior Quick scripts for experimentation
docopt Uses docstring as spec Can get messy Simple CLIs where docs are already written

For a DevOps tool that must be robust and deployable on minimal systems, argparse is often the smartest default. It's already in the standard library, so no extra requirements.txt entries — a blessing when you're shipping a single script to a locked-down server.

Troubleshooting & edge cases

Here are the common pitfalls when you create a DevOps CLI with argparse, and how to fix them.

1. error: the following arguments are required: service

You forgot to provide a positional argument. Check your add_argument calls and the run command. If you intended it to be optional, add nargs='?' or make it a flag like --service.

2. Choices validation fails with a confusing list

If you see invalid choice: 'prod ' — that's a space in the input. Sanitize input with .strip() or use type=str.lower to normalize case.

3. --verbose not incrementing

If you use action='store_true', you can't count. Use action='count' and a default of 0. Also, remember that -vv only works if you define -v as the short flag.

4. Exiting code 2 on bad input

argparse calls sys.exit(2). If you're running in a shell script, this is correct. But in unit tests, you'll need to catch SystemExit. Wrap your parse call in a function so it's testable.

5. Subparser required error

If you use subcommands, declare required=True (Python 3.7+) or check args.command manually to avoid silent no-ops.

What you learned & what's next

You now know how to create a DevOps CLI with argparse: parse arguments, validate choices, support flags, and even build subcommand-based tools. You practiced with health checks, restarts, and deployments with --dry-run and verbosity. These skills directly enable you to replace half a page of handwritten runbook instructions with a single, safe, self-documenting command.

Now you're ready to move to the next lesson in the Python for DevOps automation track, where you'll wrap these CLI patterns into a full automation library — combining argparse with config loading, logging, and error handling to build production-grade tools. Start there to take your CLI from useful to indispensable.

Practice recap

Your turn: Extend the restart.py example to include a --timeout option (in seconds) and a --force flag. Make --force required for production (--env prod) to simulate a safety gate. Run it with --help to see your new options, then test with and without --dry-run. This solidifies the pattern of adding real-world guardrails to your own CLI.

Common mistakes

  • Forgetting to call parse_args() — your script runs but ignores all user input.
  • Using store_true for verbosity instead of count — you can't do -vv.
  • Not specifying choices for environment names, leading to typos like prdo.
  • Hard-coding sys.argv parsing instead of letting argparse handle --help and errors.

Variations

  1. Use click or typer for a more modern decorator-based CLI with auto-completion.
  2. Add subcommands for multi-tool CLIs (like kubectl does) using add_subparsers.
  3. Support config file merging with --config in addition to CLI flags.

Real-world use cases

  • A deployment script that accepts --env prod --dry-run to safely test releases before applying.
  • A log-tailer that takes --service and --lines to fetch the last N lines from a remote host via SSH.
  • A backup tool that runs with --schedule daily --retention 30 and pushes archives to S3.

Key takeaways

  • argparse turns your repetitive shell commands into a single, documented CLI.
  • Use add_argument with choices, type, action, and count to build safe, flexible interfaces.
  • Subcommands let you build multi-tool CLIs (deploy, rollback, status) in one script.
  • --dry-run is a critical safety feature for any DevOps command that changes state.
  • Always handle SystemExit in tests when calling parse_args() to avoid failing your test suite.
  • Start with argparse; switch to click/typer only when you need advanced features.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.