Hello! Welcome back.
In our last lesson, we explored Go's standard flag package, which is perfect for building simple CLI tools with a flat command structure. However, as a DevOps engineer, you know that powerful tools like docker, kubectl, or git rely on a richer structure with subcommands (e.g., docker run, kubectl get pods). This is where the standard flag package falls short.
Today, we'll level up our tool-building skills. Your learning outcome is to build robust CLI applications with subcommands using a library like Cobra. Cobra is the de facto standard in the Go ecosystem for creating powerful, modern CLIs. It provides the framework for organizing commands, subcommands, and flags in an intuitive and scalable way. By the end of this lesson, you'll be able to scaffold a new CLI project and build a command hierarchy with both global and command-specific flags.
1. What is Cobra?
Cobra is a library that simplifies the creation of complex CLI applications. It's built on a simple philosophy: your CLI's actions should read like sentences. It structures your application around three core concepts:
- Commands: These are the actions, or verbs (e.g.,
add,list,get). - Arguments: These are the things, or nouns, that commands act upon (e.g., a file name or a resource ID).
- Flags: These are modifiers for the commands (e.g.,
--verbose,--output=json).
This structure allows you to build CLIs that follow a familiar pattern: app-name <command> [subcommand] [args] --flags. For a quick overview of what Cobra is and why it's so popular, the following video provides an excellent introduction.
Take Command of Go with Cobra, Go / Golang Cobra Tutorial
Watch this introductory segment to get a high-level understanding of Cobra's purpose and the core concepts it's built upon.
Watch from the beginning to 02:38. The speaker explains the benefits of Cobra and its basic structure, mentioning popular tools like Kubernetes and the GitHub CLI that are built with it.
2. Getting Started with the Cobra Generator
While you can structure a Cobra application manually, the library provides a companion CLI tool, cobra-cli, to scaffold your project. This is a huge time-saver and aligns with your preference for practical, hands-on learning. It generates a recommended project layout, allowing you to focus on the logic of your commands.
First, you'll need to install the generator:
go install github.com/spf13/cobra-cli@latest
Once installed, you can initialize a new project. The video below demonstrates this process clearly, creating a simple project and explaining the files it generates.
#63 Golang - Cobra CLI Explained: Build Advanced CLI Apps
Follow this demonstration to scaffold your first Cobra project using the cobra-cli tool.
Watch from 01:27 to 03:09. Pay attention to the cobra-cli init command and the resulting file structure, especially main.go and cmd/root.go.
After running cobra-cli init, you'll see a standard project structure.

The key files are:
main.go: A minimal entry point that executes the root command. You rarely need to touch this file.cmd/root.go: This is the heart of your CLI. It defines therootCmd, which is the base command that runs when your application is called without any subcommands.
3. Building a Command Hierarchy
Now that we have a project structure, let's add some functionality. Commands are represented by cobra.Command structs. Each command has several important fields:
Use: A one-line usage message (e.g., "get [resource-type]").Short: A short description shown in help output.Long: A more detailed description.RunorRunE: A function that contains the logic to be executed.RunEis preferred as it allows you to return an error, which is idiomatic in Go.
Adding Subcommands
You can add subcommands using the generator with cobra-cli add <command-name>. This creates a new file in the cmd directory and automatically wires it up to the rootCmd.
The following video from the codeHeim channel shows how to add a hex subcommand and define a flag for it.
#63 Golang - Cobra CLI Explained: Build Advanced CLI Apps
This clip demonstrates how to add a subcommand and define its associated flags and logic.
Watch from 03:09 to 06:05. Notice how cobra-cli add hex creates cmd/hex.go. Pay attention to how the Run function is defined and how a flag for 'length' is added in the init() function.
You can also create nested commands by adding a command to another subcommand instead of the root. For example, you could have my-app tool hash where hash is a subcommand of tool.
This image from the GoLand IDE guide shows what a typical subcommand file (subtract.go) looks like. Note the subtractCmd variable and how it's added to rootCmd in the init() function.

4. Flags: Persistent vs. Local
Just like the flag package, Cobra lets you define flags. However, it introduces a powerful distinction that's essential for building complex CLIs:
- Local Flags: These flags are only available on the specific command where they are defined. Use
cmd.Flags(). - Persistent Flags: These flags are available on the command where they are defined and on all of its subcommands. This is perfect for global settings like
--verboseor--config. Usecmd.PersistentFlags().
The video "Take Command of Go with Cobra" gives a clear explanation of this concept.
Take Command of Go with Cobra, Go / Golang Cobra Tutorial
Watch this segment to understand the crucial difference between persistent and local flags.
Watch from 07:25 to 11:11. The speaker adds a persistent flag to the root command and a local flag, demonstrating how the persistent flag is inherited by subcommands while the local one is not.
The article "How to Build a CLI Tool in Go with Cobra" provides excellent code examples for a practical taskctl tool. It shows how rootCmd defines persistent flags for global settings and how subcommands like addCmd define their own local flags.
How to Build a CLI Tool in Go with Cobra
For a detailed code reference, review these sections. They provide a complete example of a root command with persistent flags and an add subcommand with local flags.
Skim through the code in the sections "Building the Root Command" and "The Add Command". In root.go, notice the use of rootCmd.PersistentFlags() for verbose and config. In add.go, notice the use of addCmd.Flags() for priority and due.
5. Handling Arguments and Command Logic
Cobra also provides robust mechanisms for validating arguments and handling command execution.
Argument Validation
Instead of manually checking len(os.Args) as we did with the flag package, Cobra provides built-in validators that you can assign to the Args field of your command.
cobra.ExactArgs(1): The command must be called with exactly one argument.cobra.MinimumNArgs(1): The command must be called with at least one argument.cobra.NoArgs: The command must be called with no arguments.
This makes your code cleaner and more declarative.
Required Flags
You can make a flag mandatory. If a required flag is not provided by the user, Cobra will automatically report an error.
// In the init() function, after defining the flag:
myCmd.MarkFlagRequired("my-flag-name")
The RunE Function
As mentioned, it's best practice to use the RunE field for your command's logic. It has the signature func(cmd *cobra.Command, args []string) error. If your logic encounters a problem, you can return an error, and Cobra will handle printing it to the user.
This final video clip demonstrates using RunE, marking a flag as required, and implementing argument validation.
Take Command of Go with Cobra, Go / Golang Cobra Tutorial
This clip ties everything together, showing how to handle errors, require flags, and validate arguments.
Watch from 11:11 to 12:45 to see argument validation with MinimumNArgs. Then, skip to 22:23 and watch until 26:40 to see how to use RunE to return an error and how to use MarkFlagRequired.
Test your understanding!
Imagine you're building a CLI tool called cloudctl to manage cloud infrastructure. You need to implement the following command structure:
cloudctl server create <server-name> --region <region-name> --image <image-name>
cloudctlis the root command.serveris a subcommand.createis a subcommand ofserver.<server-name>is a required positional argument for thecreatecommand.--regionis a required string flag for thecreatecommand.--imageis an optional string flag for thecreatecommand, with a default value of "ubuntu-22.04".
Describe, in plain English or pseudocode, how you would structure this using Cobra. Where would you define each command and flag? Which argument validator would you use?
Show answer
- Project Setup: Run
cobra-cli initto create the project. This gives youcmd/root.go. serversubcommand: Runcobra-cli add server. This createscmd/server.go. TheserverCmdwon't have aRunfunction itself; it just acts as a parent for other server-related commands.createsubcommand: Runcobra-cli add create -p 'serverCmd'. The-pflag tells the generator to makecreatea child ofserverCmd. This createscmd/create.go.createCmdLogic:- In
cmd/create.go, edit thecreateCmd. - Set
createCmd.Args = cobra.ExactArgs(1)to ensure the<server-name>is provided. - In the
RunEfunction, access the server name from theargs[0].
- In
- Flags for
createCmd:- Inside the
init()function ofcmd/create.go: - Define the
--regionflag usingcreateCmd.Flags().StringVarP(...). - Mark it as required:
createCmd.MarkFlagRequired("region"). - Define the
--imageflag usingcreateCmd.Flags().StringVarP(...), setting its default value to"ubuntu-22.04". This flag will be optional.
- Inside the
Conclusion
You now have the fundamental skills to build sophisticated, production-grade CLI tools in Go. By leveraging Cobra, you can create applications with a clear, hierarchical structure that is both easy for you to maintain and intuitive for your users.
Key Takeaways:
- Cobra is the standard library for building CLIs with subcommands.
- The
cobra-cligenerator tool scaffolds your project with a recommended structure. - Commands are organized hierarchically by adding child commands to parent commands (e.g.,
rootCmd.AddCommand(serverCmd)). - Persistent flags (
PersistentFlags()) are inherited by subcommands, perfect for global settings. - Local flags (
Flags()) are specific to the command on which they are defined. - Cobra provides built-in argument validators (
Argsfield) and allows you to mark flags as required, reducing boilerplate code.
Next Up
Most DevOps tools need to interact with the local system. In our next lesson, we will focus on a core competency for any automation script: performing file system operations like reading from and writing to files. We will see how to use Go's standard library to build tools that can create configuration files, read log data, and manage local artifacts.