CLI for DevOps in Go and Python: Building Automation Utilities

Manual operations consume team time and become a source of failures as the number of services and the load grow. We build turnkey CLI tools for DevOps: we break down the tasks, design the utilities, and hand the team a ready solution with documentation. Repetitive actions take minutes instead of hours, and the outcome no longer depends on a single person.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1359
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Why DevOps CLI tools break as the team grows

Almost every team has a script that “does the deploy.” It lives in one engineer's home folder, relies on local environment variables, a manually maintained host list, and an access key that no one else has.

While there are five services, this noticeably saves time on routine operations. When there are thirty services and ten people, the same script becomes a single point of failure.

Fragile custom utilities are not versioned alongside service code: there is no change history, no version tag, no pre-apply checks.

A change to deployment parameters goes out as a message in a general chat rather than a repository entry, so no one can reproduce the state of the staging environment from a week ago.

The utility silently executes the command both in production and in test, because only a variable that one must remember distinguishes them.

The most costly thing is an operator error during deployment — its consequences are not a broken release, but hours spent investigating behavior that no one designed.

At the same time, the time for routine operations grows: environment preparation balloons from two commands to a dozen, and half are done manually according to instructions that have diverged from reality.

The root of the problem is not people's qualifications, but the lack of engineering requirements for the utilities themselves: a buildable artifact, a single entry point, verifiable return codes, explicit parameters instead of the environment, and the same result on any machine.

Below we break down how this is addressed — from inventorying automation scripts to DevOps CLI tools in Go or Python with tests, access control, and predictable rollback.

What routine operations automation delivers: the effect in numbers

The effect of automation is measured not by the number of scripts written, but by four metrics: time for a typical operation, share of failed runs, onboarding speed for a new engineer, and reproducibility of the result. Below is what changes in each of them when routine work is covered by custom CLI tools.

Time for a typical operation

Environment preparation, running migrations, collecting diagnostics from a dozen hosts — manually this is 20–40 minutes of waiting and context switching.

Ready-made declarative Ansible playbooks are not always enough, and then a utility in Go or Python repeats the same scenario in one to three minutes: the command can be invoked from the terminal or from the build system, and progress and the result are visible in one place.

Share of failed runs

Manual parameter entry is a source of typical failures: mixed-up environment, missed step, forgotten flag. The tool fixes the order of actions in code and validates input before starting, so an invalid value is cut off with a clear error, not in the middle of a change. The share of failed runs drops severalfold.

Time to onboard a new engineer

A newcomer gets the same set of commands with parameter descriptions instead of verbal instructions and long memos. They run a production operation on their very first day and see exactly what the system does at each step. Ramp-up to on-call shifts shrinks from weeks to a few days.

Reproducibility of the result

The same invocation produces the same result in staging and in prod: environment parameters are described in configuration, and the configuration is versioned alongside the code and goes through review.

Rollback is re-running the previous version of the utility, not reconstructing a sequence of actions from memory.

Solution formats: comparing five development approaches

The format is chosen not by developer taste, but by two things: how often the external interface changes and how many people will maintain the code six months after delivery.

Below are five schemes that teams realistically choose between at the start, with their strengths and maintenance cost.

Option When it fits Limitations
Utility from scratch Non-standard checks are needed, custom return-code logic, and precise argument parsing Longer to first result: tests, an execution scheme, and a clear version release cycle are needed
Wrapper over ready-made commands Typical operations with uniform parameters: build, deployment, state snapshot capture Inherits the behavior of the external tool and breaks when its output format changes
Set of shell scripts Rare and one-off tasks, a short sequence of calls Weak error handling, hard to cover with checks and hand over to another engineer
Extension of an open-source tool The base functionality already covers most of the task, just a couple of steps are missing Dependence on someone else's development pace and on the rules for contributing changes to someone else's code
Internal service with a dashboard Operations are performed by different roles, access separation and an action log are needed More components to maintain, harder to run on a developer's machine

A wrapper wins where an operation repeats every day and requires the same parameters for everyone.

A utility from scratch is justified when the validation rule is non-standard and cannot be expressed by calling a ready-made command; the other options cover rare operations or access separation.

The selection criterion is one: how many manual steps disappear and how quickly an engineer restores the system after a failure without rereading the instructions.

Stages of CLI utility development: from task analysis to handover

The stages are arranged so that the utility addresses a specific operational task rather than rewriting manual actions into a new wrapper. First we record what engineers do by hand, and only then decide what to hand over to the command line.

  1. Operation analysis. We break down the procedure step by step: how long each takes, where mistakes happen, what input data is needed. The output is a list of operations worth automating.

  2. Command-line interface design. We fix command and flag names, defaults, return codes, and two output modes: tabular for humans and machine-readable for scripts. We agree on the syntax before code.

  3. Utility build. We split logic into modules, move configuration out of code, and take secrets from a vault. We make repeated runs safe: the operation either runs to completion or does not change state.

  4. Integration with systems. We connect the image registry, orchestrator, logging system, and access control. Permissions are role-based, destructive actions only with explicit confirmation.

  5. Automation testing. We run scenarios on a copy of the environment, including failures: an unavailable external system, a broken connection, an interrupted run. The utility must return a clear return code and leave no garbage behind.

  6. Handover. We provide documentation, command reference, and run examples, and walk through changes with the on-call shift. We separately record what to do when external systems change.

What the team retains after automation implementation

The most important thing about automation implementation is that afterward the team retains not screenshots and not “works on my machine,” but a reproducible set of artifacts.

A utility in Go covers system calls and file operations, Python covers API glue and reports; both are built from source with one command, dependencies are pinned, so any engineer brings up the environment in a few minutes.

  • Utility source code and reproducible build — modules with clear names, configuration via environment variables and a settings file, build with one command without manual edits.
  • Command documentation — every flag, mode, return code, and invocation example; an on-call engineer runs the task without the utility's author.
  • Integration diagrams — which systems the utility reads and changes, where permission boundaries lie, which failure points are covered by retries.
  • Access permission matrix — which token and role are needed for a specific operation, where only reading is allowed, and where writing requires confirmation.
  • Automated checks — unit tests for parsing responses and integration runs in staging, triggered on every change in the build pipeline.
  • Update policy and rollback — who owns the utility, how a new version is released, how to return to the previous one with a single command.
  • Support guide — typical failures, where to look at logs, what to do when an external system is unavailable, whom to escalate to.

The package is handed over together with knowledge transfer: a code walkthrough with your engineers so that support does not depend on us.

Case study: how manual operations and the number of failures were reduced

What it was like

A team of eight engineers ran infrastructure on Kubernetes: about forty services across three environments.

Environment preparation took from three hours — some steps were done manually according to a checklist, some with one-off scripts that were not portable between environments. During configuration deployment they compared by eye, so roughly every sixth release was rolled back.

A new engineer was onboarded over three weeks: they read the policy, repeated others' actions, and made mistakes in the order of steps.

What we did

We built command-line utilities covering the path from request to environment and from commit to deployment: one command creates an environment from a description, the second validates configurations before applying, the third rolls back to the previous working version. The logic lives in a repository, and each utility has a dry-run mode.

Metric Before After
Environment preparation 3 hours manually 12 minutes with one command
Share of rolled-back deployments ~17% 3%
Time to first independent deployment 3 weeks 5 days
Manual steps in the release cycle 20+ 4 verifiable

The result rests on the predictability of tools, not discipline: discrepancies are caught before applying, rollback takes minutes, the policy is replaced by an executable command that cannot be forgotten.

Technical concerns before starting work

Before starting, the concerns are usually the same: what access is needed, what happens if a failure occurs midway through an operation, where to verify the result, and who will support the utility afterward. Below are short answers to the most frequently asked questions.

What access do you request?

A separate service account for the task, without admin rights. Permissions are minimally sufficient, tokens live in a secrets vault with a limited lifetime, all calls are written to a log. There are no credentials in plaintext in code or configs.

What happens if an operation is interrupted midway?

Mutating commands are idempotent: a repeated run reaches the stopping point and continues from there. State is written to a local log, so interruption by signal finishes the current step and does not leave the system half-changed.

Can we test on a copy of the environment first?

Yes. The utility is parameterized by address and environment profile, so the same code runs in staging and in production. There is a preview mode — commands only print the change plan without applying anything.

How are dependency and environment issues handled?

Library versions are pinned in a lock file, and the target service version is checked at startup. The build runs with one command in an isolated environment, so the result does not depend on what is installed on the engineer's machine.

Will we be able to maintain the utility on our own?

We hand over the source code, tests, command descriptions, and run examples, and separately walk through the code with your team. Adding a new scenario is one command, not rewriting the core.

Let's discuss the task and come back with an estimate and plan

To come back with an estimate and plan rather than generalities, we need a minimum of context: which operations the team performs by hand, in which environments, what you have already tried to automate, and where it fell apart.

Send a list of routine actions, command examples, and available ways to connect to systems — that is enough to discuss the automation task concretely.

The scoping takes one meeting with the engineer who will lead the work: we clarify the boundaries, agree on acceptance criteria, and fix the scope of the first stage. Then a pilot run in one environment, and only based on its results, expansion to the others.

The first stage usually shapes up as follows:

  • inventorying routine work and measuring how much time it takes from the team
  • choosing a language for the task: Go — when a single binary without dependencies is needed, Python — when there is a lot of work with external systems
  • utility contract: a set of subcommands, flags, return codes, machine-readable output
  • configuration via environment variables and files, secrets — outside the repository
  • pilot run in one environment and comparison with a manual run by time and number of errors
  • checks for parsing system responses and for error codes
  • handover: repository, documentation, invocation examples, and a walkthrough with your team

Describe the task in free form — we will come back with an estimate, work plan, and pilot scope.

How is a ready-made utility different from a set of scripts?

A script solves the task today — a utility sustains it for years. The difference shows not on the first run, but on the hundredth: a script requires its author to be nearby, while a utility requires only documented parameters and a clear return code.