The cmd Directory and Multiple Binaries in Go
How to build several programs from one Go module with cmd folders, keep main packages thin, and build or install each binary.
A single module often produces more than one program: an API server, a background worker, an admin CLI. Each program needs its own package main with its own func main(). The usual convention is one folder per program under cmd/.
The Layout
shop/
go.mod module example.com/shop
cmd/
shop-api/main.go package main
shop-worker/main.go package main
internal/
order/order.go package order
The folder name becomes the binary name. go build ./cmd/shop-api produces an executable called shop-api (or shop-api.exe on Windows).
Building and Running Each Program
go run ./cmd/shop-api
go build ./cmd/shop-worker
go build -o bin/ ./cmd/...
go install ./cmd/shopctl
go run ./cmd/shop-apicompiles and runs one program.go build -o bin/ ./cmd/...builds every program undercmd/into abin/folder.go installputs the binary in$GOBIN, or$HOME/go/binby default.
Other people can install a program from your module directly:
go install example.com/shop/cmd/shopctl@latest
Two main packages in one folder:
Putting server.go and cli.go, each with func main(), in the same folder fails with main redeclared in this block. Each program needs its own folder.
Keep main Thin
The code in a cmd/ folder should only start the program: read flags and config, create dependencies, and run. A clean pattern is a run function that returns an error:
// file: cmd/greeter/main.go
package main
import (
"flag"
"fmt"
"io"
"os"
)
func run(args []string, out io.Writer) error {
fs := flag.NewFlagSet("greeter", flag.ContinueOnError)
name := fs.String("name", "world", "who to greet")
if err := fs.Parse(args); err != nil {
return err
}
fmt.Fprintf(out, "hello, %s\n", *name)
return nil
}
func main() {
if err := run(os.Args[1:], os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Running go run ./cmd/greeter -name asha prints hello, asha. Because run takes its arguments and output writer as parameters, a test can call it directly and check the output without starting a process.
Business logic, such as how orders are priced, belongs in internal/ packages, so both the API and the worker can share it.
One Binary at the Root Is Fine Too
If a module only ever builds one program, main.go at the root is simpler, and go install example.com/mytool@latest then installs a binary named mytool. Move to cmd/ when a second program appears.
Importing a main package:
A package main cannot be imported by other packages. If two programs need the same code, it must move into a normal package such as internal/order. Trying to import a cmd/ folder fails with import "example.com/shop/cmd/shop-api" is a program, not an importable package.
Well-organized binaries:
Each program has its own folder under cmd/, each main.go is short and mostly wiring, and all shared logic lives in importable packages under internal/.
Summary
Put each program in its own folder under cmd/, where the folder name becomes the binary name. Build them with go build ./cmd/name, all at once with ./cmd/..., or install them with go install. Keep main packages thin by moving logic into internal/ packages and using a run function that returns an error, and stay with a root main.go while you only have one program.