# How to Structure Multi-Binary Go Projects: The Standard Layout Explained

> Structure multi-binary Go projects effectively using the standard layout. Organize code in cmd internal and pkg directories for maintainable entry points.

- Repository: [golang-standards/project-layout](https://github.com/golang-standards/project-layout)
- Tags: architecture
- Published: 2026-03-06

---

**Place each binary in its own subdirectory under `/cmd`, share private implementation details in `/internal`, and expose public APIs in `/pkg` to keep entry points thin and maintainable.**

Multi-binary Go projects—repositories that ship several executables such as APIs, background workers, and administrative CLI tools—require careful organization to avoid circular dependencies and tight coupling. The **golang-standards/project-layout** repository provides a battle-tested blueprint for structuring these projects, used by major Go codebases like **Prometheus**, **Kubernetes**, and **Velero**.

## The Core Directories: `/cmd`, `/internal`, and `/pkg`

Three directories form the backbone of any multi-binary Go project. Each serves a distinct purpose in the dependency graph, ensuring that shared code remains accessible to your binaries while hidden from external consumers.

### `/cmd` – One Directory Per Binary

The **`/cmd`** directory contains one subdirectory for every executable your project produces. According to the project's [`cmd/README.md`](https://github.com/golang-standards/project-layout/blob/main/cmd/README.md), each folder must be named after the final binary it generates (e.g., `cmd/api`, `cmd/worker`, `cmd/admin`). Inside each folder, a single [`main.go`](https://github.com/golang-standards/project-layout/blob/main/main.go) file acts as a thin bootstrap layer that wires together reusable packages.

This isolation prevents entry points from accumulating business logic. When you run `go build ./cmd/api`, the compiler produces a binary named `api` that imports logic from elsewhere, keeping the `main` package clean.

### `/internal` – Private Shared Implementation

The **`/internal`** directory houses packages that cannot be imported by external modules. As documented in [`internal/README.md`](https://github.com/golang-standards/project-layout/blob/main/internal/README.md), the Go compiler enforces this boundary—any package under an `internal` directory is accessible only to code within its parent module tree.

This is where you place **configuration loaders**, **database adapters**, and **business logic** shared across your binaries. For example, `internal/app/api` might contain HTTP handlers, while `internal/app/worker` contains queue consumers, both importing `internal/pkg/config` for shared initialization logic.

### `/pkg` – Public Reusable Libraries

The **`/pkg`** directory contains libraries intended for external consumption. If a component of your project is useful to other Go modules—such as a generated gRPC client or a metrics wrapper—place it here. External projects can import these packages via `go get`, while your own binaries in `/cmd` treat them as first-party dependencies.

## Real-World Directory Structure

A typical multi-binary project with an API server, background worker, and admin CLI tool follows this layout:

```

myproject/
├─ cmd/
│  ├─ api/
│  │  └─ main.go          # Entry point for the API server

│  ├─ worker/
│  │  └─ main.go          # Entry point for the background worker

│  └─ admin/
│     └─ main.go          # Entry point for the admin CLI

├─ internal/
│  ├─ app/
│  │  ├─ api/            # Business logic for the API binary

│  │  ├─ worker/         # Business logic for the worker binary

│  │  └─ admin/          # Business logic for the admin binary

│  └─ pkg/
│     └─ config/         # Private shared packages (e.g., config loader)

├─ pkg/
│  └─ client/            # Public library for external projects

├─ configs/
│  ├─ api.yaml
│  ├─ worker.yaml
│  └─ admin.yaml
└─ go.mod

```

Each `cmd/*/main.go` imports from `internal/app/...` for domain logic and `pkg/...` for public utilities. Ancillary assets like configuration templates live in `/configs`, with one file per binary to allow environment-specific tuning.

## Why This Layout Works with Go Tooling

The Standard Go Project Layout aligns with the compiler's built-in protections and community conventions. The **`internal`** directories are enforced by the toolchain—attempting to import `github.com/yourorg/myproject/internal/pkg/config` from an external module results in a compilation error. This guarantees encapsulation without relying on documentation alone.

Furthermore, placing binaries under **`/cmd`** mirrors the structure used by the Go ecosystem's largest projects. The [`cmd/README.md`](https://github.com/golang-standards/project-layout/blob/main/cmd/README.md) in the golang-standards repository explicitly references **Prometheus**, **Kubernetes**, and **Velero** as projects following this convention. Because the layout uses a single `go.mod` at the root, all binaries share versioned dependencies while remaining importable via distinct subpaths.

## Implementing the Pattern

The following examples demonstrate how to keep [`main.go`](https://github.com/golang-standards/project-layout/blob/main/main.go) files minimal while maximizing code reuse across binaries.

### Entry Point in [`cmd/api/main.go`](https://github.com/golang-standards/project-layout/blob/main/cmd/api/main.go)

This entry point imports internal business logic and a public client library, delegating all work to imported packages:

```go
package main

import (
    "log"

    "github.com/yourorg/myproject/internal/app/api"
    "github.com/yourorg/myproject/pkg/client"
)

func main() {
    cfg, err := api.LoadConfig()
    if err != nil {
        log.Fatalf("config error: %v", err)
    }

    c := client.New(cfg.APIKey)
    if err := api.StartServer(c, cfg); err != nil {
        log.Fatalf("server failed: %v", err)
    }
}

```

The `api` package (located in `internal/app/api`) handles server initialization, while `client` (from `pkg/client`) provides a public API for external HTTP calls.

### Shared Configuration in [`internal/app/config/config.go`](https://github.com/golang-standards/project-layout/blob/main/internal/app/config/config.go)

Place reusable initialization code in `internal` to share across binaries without exposing implementation details:

```go
package config

import "github.com/spf13/viper"

type Config struct {
    APIKey string `mapstructure:"api_key"`
    Port   int    `mapstructure:"port"`
}

// LoadConfig reads a YAML file from ./configs/<binary>.yaml.
func LoadConfig(name string) (*Config, error) {
    v := viper.New()
    v.SetConfigName(name)
    v.AddConfigPath("./configs")
    v.SetConfigType("yaml")
    
    if err := v.ReadInConfig(); err != nil {
        return nil, err
    }

    var cfg Config
    if err := v.Unmarshal(&cfg); err != nil {
        return nil, err
    }
    return &cfg, nil
}

```

Both [`cmd/api/main.go`](https://github.com/golang-standards/project-layout/blob/main/cmd/api/main.go) and [`cmd/worker/main.go`](https://github.com/golang-standards/project-layout/blob/main/cmd/worker/main.go) can call `config.LoadConfig("api")` or `config.LoadConfig("worker")`, ensuring consistent configuration parsing while keeping binary-specific settings isolated in `/configs`.

### Public Client Library in [`pkg/client/client.go`](https://github.com/golang-standards/project-layout/blob/main/pkg/client/client.go)

Libraries intended for external consumption live in `/pkg` with exported functions:

```go
package client

type Client struct {
    APIKey string
}

// New creates a client instance.
func New(apiKey string) *Client {
    return &Client{APIKey: apiKey}
}

// DoSomething is an exported function usable by any Go module.
func (c *Client) DoSomething() error {
    // implementation
    return nil
}

```

External projects import `github.com/yourorg/myproject/pkg/client`, while your internal binaries treat it as a standard dependency.

## Summary

- **Use `/cmd/<binary>/main.go`** for each executable to isolate entry points and keep bootstrap code minimal.
- **Store private shared code in `/internal`** to leverage the compiler's import restrictions and protect implementation details.
- **Place public libraries in `/pkg`** to expose stable APIs for external consumption.
- **Reference the golang-standards/project-layout** repository for community-validated conventions used by Kubernetes, Prometheus, and Velero.

## Frequently Asked Questions

### What is the difference between `/internal` and `/pkg`?

The **`/internal`** directory contains packages that the Go compiler restricts to the current module—external projects cannot import them. This is ideal for business logic and database adapters. The **`/pkg`** directory contains public libraries intended for external consumption, allowing other Go modules to import and reuse your code via `go get`.

### How do I handle configuration files for multiple binaries?

Create a **`/configs`** directory at the project root with one configuration file per binary (e.g., [`api.yaml`](https://github.com/golang-standards/project-layout/blob/main/api.yaml), [`worker.yaml`](https://github.com/golang-standards/project-layout/blob/main/worker.yaml)). Store a shared loader in `internal/pkg/config` that accepts the binary name as a parameter, allowing each `cmd/*/main.go` to load its specific settings while reusing parsing logic.

### Can external projects import code from my multi-binary repository?

Yes, but only from **`/pkg`** and any other top-level directories outside of `/internal` or `/cmd`. The `internal` directory is protected by the Go compiler, ensuring that your implementation details remain private. Public APIs in `/pkg` should be versioned and documented for stability.

### Where should I put business logic that is shared between binaries?

Place domain logic in **`/internal/app/<binary>`** for code specific to one executable, and in **`/internal/pkg`** or **`/internal/app/common`** for utilities shared across multiple binaries. This keeps shared code private to your module while allowing each binary to import exactly what it needs.