Nomad — Structure#
Layout pattern#
Custom / Domain-Driven Layout (not standard Go cmd/internal/pkg)
Nomad uses a domain-driven layout where top-level directories represent major functional subsystems rather than the conventional cmd/, internal/, pkg/ trio. There is no cmd/ directory — the single main.go lives at the repo root. The primary server code lives in nomad/, client code in client/, scheduler in scheduler/, and so on. This mirrors HashiCorp’s house style seen across Vault and Consul: large, named-by-purpose packages at the root level, with a shared helper/ package providing cross-cutting utilities.
Directory map#
nomad/ # Root — single binary project
├── main.go # Entry point: CLI dispatcher + subcommand init-hooks
├── acl/ # ACL policy engine (evaluation, not HTTP endpoints)
├── api/ # Standalone Go module: public client library (go.mod)
│ ├── contexts/ # Context key constants for API requests
│ └── internal/ # Internal helpers for the API module
├── client/ # Nomad agent client subsystem
│ ├── allocdir/ # Allocation directory management (volumes, chroots)
│ ├── allochealth/ # Alloc health tracking (checks + services)
│ ├── allocrunner/ # Core: runs a single allocation (hooks, task lifecycle)
│ ├── allocwatcher/ # Watches remote alloc state for migration
│ ├── commonplugins/ # Shared plugin initialization for client
│ ├── config/ # Client configuration struct + parsing
│ ├── consul/ # Consul service registration on client side
│ ├── devicemanager/ # Plugin manager for device drivers (GPU, etc.)
│ ├── dynamicplugins/ # Registry for dynamically-loaded plugins (CSI)
│ ├── fingerprint/ # System fingerprinting (CPU, memory, network)
│ ├── hoststats/ # Host resource stats collector
│ ├── hostvolumemanager/ # Host volume lifecycle management
│ ├── interfaces/ # Client-internal interfaces (AllocRunner, etc.)
│ ├── lib/ # Client-internal utilities
│ ├── logmon/ # Log monitoring process (runs as child subprocess)
│ ├── pluginmanager/ # Plugin lifecycle management base
│ ├── servers/ # Client-side server list and load balancing
│ ├── serviceregistration/ # Abstraction over Consul/Nomad service registration
│ ├── state/ # Client state persistence (BoltDB)
│ ├── structs/ # Client-specific structs
│ ├── taskenv/ # Task environment variable interpolation
│ ├── testutil/ # Client test helpers
│ ├── vaultclient/ # Vault token management on client side
│ └── widmgr/ # Workload Identity manager
├── command/ # CLI command implementations + HTTP API (agent)
│ ├── agent/ # HTTP API server, agent lifecycle, config parsing
│ └── ui/ # Embedded web UI assets
├── drivers/ # Built-in task drivers
│ ├── docker/ # Docker driver + docker logger subprocess
│ ├── exec/ # Linux exec driver (namespaces/cgroups)
│ ├── java/ # Java driver
│ ├── mock/ # Mock driver for testing
│ ├── qemu/ # QEMU VM driver
│ ├── rawexec/ # Raw exec driver (no isolation)
│ └── shared/ # Shared driver utilities (executor, env)
├── e2e/ # End-to-end test suites (~40 scenarios)
├── helper/ # Cross-cutting utility packages (~45 sub-packages)
├── internal/ # Internal test helpers
├── jobspec2/ # HCL2 job specification parser
├── lib/ # Shared algorithms (auth, heaps, file utils)
├── nomad/ # Server subsystem: Raft FSM, RPC endpoints, scheduler workers
│ ├── auth/ # Server-side authentication/authorization
│ ├── deploymentwatcher/ # Deployment health and promotion logic
│ ├── drainer/ # Node drain orchestration
│ ├── lock/ # Distributed locking subsystem
│ ├── mock/ # Server mock data for tests
│ ├── peers/ # Raft peer management helpers
│ ├── reporting/ # Usage telemetry/reporting
│ ├── state/ # Raft state store (memdb)
│ ├── stream/ # Event streaming (subscribe/publish)
│ ├── structs/ # Core domain structs (Job, Alloc, Node, …)
│ └── volumewatcher/ # CSI volume claim watcher
├── plugins/ # Plugin SDK (task drivers, device, CSI)
│ ├── base/ # Base plugin interface + fingerprinting
│ ├── csi/ # CSI plugin interface
│ ├── device/ # Device plugin interface
│ ├── drivers/ # Task driver plugin interface
│ └── shared/ # Shared plugin RPC utilities
├── scheduler/ # Standalone scheduler (bin-packing, spread, system)
│ ├── benchmarks/ # Scheduler performance benchmarks
│ ├── feasible/ # Constraint-based feasibility checking
│ ├── integration/ # Scheduler integration tests
│ ├── reconciler/ # Deployment reconciliation logic
│ ├── structs/ # Scheduler-specific structs
│ └── tests/ # Scheduler unit tests
├── testutil/ # Top-level test helpers (server/client bringup)
├── tools/ # Developer tooling (changelog, proto tools)
├── ui/ # Ember.js web UI (separate build, embedded via go:embed)
├── version/ # Version constants and build metadata
└── website/ # Documentation source (not Go code)Entry points#
Nomad ships as a single binary — there is no cmd/ directory. The single entry point is:
main.go— CLI dispatcher usinggithub.com/hashicorp/cli. Dispatches to subcommands incommand/. Importantly, it also imports several subprocess packages with_(blank identifier) so theirinit()functions run; this allows the same binary to function as a parent process or aslogmon,executor, anddocker_loggersubprocesses by inspectingos.Args[0]or environment variables at startup.
Key subcommands defined in command/:
agent— starts a Nomad server and/or client noderun/stop/status— job lifecycle managementalloc/job/node— resource inspectionoperator— Raft management, snapshot, autopilotnamespace,acl,keyring,scaling,volume— administrative commands
Package organization#
Internal packages (no internal/ convention)#
Nomad places its internal subsystems directly at root level — there is no internal/ directory enforcing import restrictions (except a tiny internal/testing/ helper). Internal-by-convention packages:
nomad/— Server: Raft FSM, all RPC endpoint handlers, scheduler worker goroutines, eval broker, plan queue. Thenomad/structs/sub-package is the canonical domain model for the entire codebase.client/— Client: allocation runner, plugin manager, fingerprinter, state persistence.scheduler/— Standalone scheduling algorithms; no server/client dependencies.drivers/— Built-in task drivers, each as its own package, using theplugins/driversinterface.command/agent/— HTTP API server (REST + WebSocket). Sits between external callers and thenomad/RPC layer.helper/— ~45 small utility packages covering crypto, TLS, logging, codec, flags, broker, etc.
Public packages (importable by external tools)#
api/— First-class public Go module (github.com/hashicorp/nomad/api). Client library for interacting with the Nomad HTTP API. Maintained with separatego.mod.plugins/— Plugin SDK interfaces: drivers, devices, CSI. External driver authors implement these.
Layering#
The dependency hierarchy flows roughly:
main.go
└── command/ (CLI, HTTP API)
└── nomad/ (Server: Raft, RPC, eval broker)
├── scheduler/ (scheduling algorithms — no server imports)
├── nomad/state/ (memdb state store)
└── client/ (client agent — separate logical node)
└── drivers/ (task execution via plugins/)
plugins/ (shared interface layer — imported by both client/ and drivers/)
nomad/structs/ (domain model — imported by almost everything)
helper/ (utilities — imported by almost everything)nomad/structs/ is the shared domain model and the most widely-imported package in the repo — a deliberate central coupling point for Job, Allocation, Node, and Evaluation types.
Build system#
- Build tool: GNU Make (
GNUmakefile) — primary build orchestration - Key targets:
make dev— build thenomadbinary with development flagsmake release— cross-compile for all platforms (linux amd64/arm64/s390x, darwin, windows, freebsd)make test— run test groups (nomad, client, command, drivers, quick) viaGOTEST_GROUPmake generate— rungo generatefor protobuf, stringer, and mock generationmake proto— regenerate protobuf bindings viabufmake ui— build the Ember.js UI and embed itmake docker— build the release Docker image
- Build tags:
ui(embeds the web UI),hashicorpmetrics,codegen_generated(CI),ent(enterprise edition) - Docker: Yes, multi-stage. Stage 1 (
alpine) adds tzdata; stage 2 (busybox) is the final minimal runtime image. The binary is injected externally (not built inside Docker).
Notable structural decisions#
Single binary, multi-role subprocess pattern. The same
nomadbinary acts as CLI, server, client agent, and also as child subprocess implementations (logmon,executor,docker_logger). Theinit()trick at the top ofmain.godrops into subprocess logic before the CLI framework initializes, avoiding memory overhead from unused imports. This is a sophisticated use of Go init-ordering that is uncommon at this scale.api/as a separate Go module. The public client library is a distinct module with its owngo.mod. This enforces that external tooling can depend on the API client without pulling in the entire server binary’s transitive dependencies (Raft, Consul SDK, etc.). The main module references it via areplacedirective during development.nomad/structs/as the monolithic domain model. Rather than distributing domain types across packages, all core types (Job, TaskGroup, Allocation, Node, Evaluation, Deployment, etc.) live in one large package. This creates broad coupling but also a single authoritative source of truth — a pragmatic choice for a system with many cross-cutting concerns.scheduler/isolated fromnomad/(server). The scheduling algorithms have no import dependency on the server package. They consume and producestructstypes and operate through an explicitStateinterface. This enables isolated unit testing of scheduling logic and, in principle, alternative scheduler implementations.Plugin system as first-class SDK. The
plugins/directory provides a versioned, interface-defined SDK for task drivers, device plugins, and CSI plugins — not just internal use but designed for external plugin authors. Combined withhashicorp/go-plugin(RPC-based subprocess plugins), this creates a clear extension boundary that third-party driver authors can target without forking Nomad.e2e/as a comprehensive integration suite. With ~40 named test scenarios covering ACLs, Consul/Vault integration, CSI volumes, networking, scaling, disconnected clients, and more, the e2e suite is a first-class artifact — it even has its ownframework/sub-package for test orchestration and Terraform configs for provisioning test clusters.