talos Documentation

Talos Linux is a modern Linux distribution built for Kubernetes.

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy talos on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name talos --restart always siderolabs/talos:latest

Technical Documentation: siderolabs/talos

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)


Talos is a modern OS for running Kubernetes: secure, immutable, and minimal.
Talos is fully open source, production-ready, and supported by the people at Sidero Labs.
All system management is done via an API - there is no shell or interactive console.
Benefits include:

  • Security: Talos reduces your attack surface: It's minimal, hardened, and immutable.
    All API access is secured with mutual TLS (mTLS) authentication.
  • Predictability: Talos eliminates configuration drift, reduces unknown factors by employing immutable infrastructure ideology, and delivers atomic updates.
  • Evolvability: Talos simplifies your architecture, increases your agility, and always delivers current stable Kubernetes and Linux versions.

Documentation

For instructions on deploying and managing Talos, see the Documentation.

Community

If you're interested in this project and would like to help in engineering efforts or have general usage questions, we are happy to have you!
We hold a monthly meeting that all audiences are welcome to attend.

We would appreciate your feedback so that we can make Talos even better!
To do so, you can take our survey.

Office Hours

  • When: Second Monday of every month at 16:30 UTC.
  • Where: Google Meet.

Note: You can convert the meeting hours to your local time.

Contributing

Contributions are welcomed and appreciated!
See Contributing for our guidelines.

License

Some software we distribute is under the General Public License family of licenses or other licenses that require we provide you with the source code.
If you would like a copy of the source code for this software, please contact us via email: info at SideroLabs.com.


Overview (`api/signer/README.md`)

Signer API

The signer API defines the gRPC service used by the Talos imager to delegate
SecureBoot and PCR signing to an out-of-process signer.

An example YubiKey-based implementation is available at:
https://github.com/jaakkonen/talos-secureboot


Overview (`hack/ctrltrace-viewer/README.md`)

ctrltrace viewer

A single-page viewer for the controller runtime traces produced by
internal/pkg/ctrltrace. It replays a capture as an animated graph: controllers and
resource types as nodes, clustered by namespace, flashing as they reconcile, with
resource nodes growing and shrinking as instances come and go.

Using it

Open index.html in a browser โ€” straight off the filesystem is fine โ€” then drop a
.jsonl capture onto the page, or use Load a trace. Nothing is uploaded; the file
is parsed in the browser.

To capture one, see the tracer's README:

sh
make talos WITH_DEBUG=1
# boot with talos.trace=1 talos.trace.specs=1
talosctl -n <node> read /run/talos-trace.jsonl > talos-boot.jsonl

The page also tries to fetch talos-boot.jsonl from its own directory on load, so
dropping a capture there and serving the directory gives you a viewer that opens
straight onto that boot:

sh
cp talos-boot.jsonl hack/ctrltrace-viewer/
python3 -m http.server -d hack/ctrltrace-viewer 8000

Themes

The Theme control in the footer picks between Auto (follows the browser), Light,
Dark and Projector; t cycles them, and the choice is remembered per browser.

Projector is the one to use on stage: white ground, near-black ink, deep blue and rust
marks instead of the screen palette's lighter steps, thicker strokes and larger type. Lecture
hall projectors crush low contrast, so the greys that read as pleasantly quiet on a laptop
disappear entirely โ€” this theme pushes everything toward black and scales every stroke,
node and label by 1.45x.

What it shows

  • Squares are controllers, circles are resource types; size follows the number
    of live instances. Position is a force layout clustered by namespace, computed once
    and frozen so the map stays put between replays.
  • A node is an empty outline until the trace first touches it, so the boot fills in the
    architecture rather than starting fully lit.
  • Selecting a node draws its edges as arrows in the direction data flows โ€” orange out
    of the controller that writes a resource, blue from a resource into the controllers
    that read it. The side panel lists the same thing in text, along with the live
    instances at the current playhead; clicking an instance shows its spec, when the
    capture was taken with talos.trace.specs=1.
  • The graph is the union of the static dependency graph embedded in the trace (the same
    one talosctl inspect dependencies renders) and the traffic actually observed, which
    in practice adds a few hundred edges the controllers never declared.
  • The scrub track is state operations per second on a square-root scale โ€” a boot is
    heavy-tailed enough that a linear axis shows one spike and nothing else.

Notes

  • The page loads d3 from cdnjs and its fonts from Google Fonts, so first paint wants a
    network connection. Everything else, including parsing a multi-megabyte trace, is local.
  • Large captures are fine: a 6 MB, 29k-event trace parses, lays out and renders in well
    under a second.

Overview (`tools/cloud-image-uploader/README.md`)

cloud-image-uploader

Image tags

Published images are tagged with their build type, which is what cleanup tooling keys off
of to decide how long an image should stick around:

  • AWS: the tag BuildType, on both the AMI and the EBS snapshot backing it.
  • GCP: the label build-type on the image. GCP label keys must be lowercase, hence the
    different spelling.

The values are the same on both:

Value Meaning
e2e throwaway image built for an e2e run
nightly branch build, versioned by git describe (e.g. v1.14.0-alpha.1-101-g11a7fbe4c)
release tagged release, including alpha/beta/rc pre-releases

The value is derived from --tag and --name-prefix.

vmimport role

Role should be pre-created before running this command.

text
aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json

Google Cloud Pre-requisites

  • GOOGLE_PROJECT_ID - Google Cloud Project ID
  • GOOGLE_CREDENTIALS_JSON - Google Cloud Service Account JSON

Overview (`tools/kubeimportlinter/README.md`)

kubeimportlinter

kubeimportlinter checks that versioned Kubernetes imports are aliased to a descriptive name.

Imports whose final path segment is a bare API version (e.g. v1, v1beta1, v2) from a Kubernetes host (k8s.io, sigs.k8s.io, ...) must be renamed:

go
import "k8s.io/api/core/v1"            // invalid: package name is bare v1
import v1 "k8s.io/api/core/v1"         // invalid: alias equals the version
import corev1 "k8s.io/api/core/v1"     // valid

It runs as a golangci-lint module plugin custom linter named kubeimportlinter.

Usage

The repo-root .custom-gcl.yml lists this module for the custom binary build:

bash
make golangci-lint-custom
_out/custom-gcl run --config .golangci.yml

Enable it in .golangci.yml:

yaml
linters:
  enable:
    - kubeimportlinter
  settings:
    custom:
      kubeimportlinter:
        type: module
        settings:
          exclude:
            - "**/*_test.go"
            - "vendor/**"
          rules:
            versioned_imports:
              # override the default host list if needed
              hosts:
                - k8s.io
                - sigs.k8s.io
              allow:
                - "path/to/exempt.go"

Config

All rules are enabled by default. An empty settings block applies the rule to
every analyzed Go file under the repository root.

  • the repository root is the directory containing the golangci-lint config file.
  • versioned_imports.hosts lists the import path prefixes the rule applies to
    (defaults to k8s.io, sigs.k8s.io).
  • allow exempts files from the rule; exclude is a broader file skip;
    include narrows the rule; enabled: false disables it.
  • globs are repo-relative, slash-separated, and support **.

Inline exceptions

go
// kubeimportlint:ignore versioned_imports intentional bare alias
v1 "k8s.io/api/core/v1"

Overview (`tools/loglinter/README.md`)

log-linter

log-linter checks Go code for logging policy violations.

It runs as a golangci-lint module plugin custom linter named loglinter.

Current checks:

  • disallowed log/slog imports unless allowlisted
  • disallowed stdlib log.* calls unless allowlisted
  • printf-style formatting directives in *zap.Logger message strings
  • fmt.Sprintf(...) used as a zap message
  • ad hoc zap root logger constructors not wrapped with .With(logging.Component(...)) in the same expression

Usage

Build a custom golangci-lint binary

A repo-root .custom-gcl.yml is included for the custom binary build.

From the repo root:

bash
make golangci-lint-custom

That builds the binary through the Docker-based flow and outputs it to _out/custom-gcl.

Run it directly:

bash
_out/custom-gcl run --config .golangci.yml

Enable the plugin in `.golangci.yml`

Add a custom linter entry like this:

yaml
version: "2"

linters:
  enable:
    - loglinter
  settings:
    custom:
      loglinter:
        type: module
        description: checks logging conventions
        settings:
          exclude:
            - "**/*_test.go"
            - "_out/**"
            - "vendor/**"
          rules:
            slog_imports:
              allow:
                - "internal/app/machined/pkg/runtime/v1alpha1/platform/vmware/vmware_supported.go"

The repository root is the directory containing the golangci-lint config file.

Config

All rules are enabled by default. An empty settings block applies every rule to every analyzed Go file under the repository root.

Use config mainly to carve out exceptions.

  • allow is the recommended way to exempt files from a specific rule.
  • exclude is a broader file skip for a rule or for the whole run.
  • include is still supported when you intentionally want to narrow a rule, but the default is repo-wide enforcement.
  • enabled: false explicitly disables a rule.
  • globs are repo-relative, slash-separated, and support **.

Inline exceptions

A single issue can be ignored with a same-line or immediately preceding comment:

go
// loglint:ignore stdlib_log_calls compatibility adapter
log.Printf("allowed here")

Multiple rules can be listed as a comma-separated token:

go
// loglint:ignore stdlib_log_calls,zap_root_component reason

Use all to suppress every rule for the next line or same line.

Notes

  • Once loglinter is enabled in .golangci.yml, use the custom binary (./custom-gcl) or make golangci-lint-custom; a stock golangci-lint binary will not have the plugin compiled in.
  • The root-component check is intentionally conservative: it only accepts .With(logging.Component(...)) when that wrapper appears in the same expression as the configured constructor call.
  • Constructor names and component helper calls are configurable.

Overview (`internal/pkg/ctrltrace/README.md`)

ctrltrace โ€” controller runtime tracing

ctrltrace records what the COSI controller runtime actually does while Talos boots:
which controller woke up, what it read, what it wrote, and when every resource appeared,
changed and went away.

It is a development facility, and it is compiled in only under WITH_DEBUG=1 (build tag
sidero.debug). A production image gets stub.go instead: no tracer, no state wrapping, no
way to turn it on. Even in a debug build nothing is wrapped or allocated until the trace is
explicitly enabled, so it costs nothing on a normal boot.

Capturing a trace

Build a debug image:

sh
make talos WITH_DEBUG=1

Then boot the node with the trace kernel arguments:

text
talos.trace=1

Optional:

parameter default meaning
talos.trace.path /run/talos-trace.jsonl output file
talos.trace.specs 0 also record resource specs (YAML) on create/update
talos.trace.duration unlimited stop tracing after e.g. 90s, keeping the file small

In a container-based cluster (talosctl cluster create --provisioner docker) set the
TALOS_TRACE=1 environment variable on the container instead.

For a QEMU cluster:

sh
talosctl cluster create \
  --extra-boot-kernel-args "talos.trace=1 talos.trace.specs=1 talos.trace.duration=3m"

Pull the trace off the node once it is up:

sh
talosctl -n <node> read /run/talos-trace.jsonl > talos-boot.jsonl

Handle the file with care

talos.trace.specs=1 records the YAML of every resource created or updated, and unlike the
API the tracer does not redact the types Talos marks Sensitivity: sensitive โ€” a secret
you cannot read is not much use when you are debugging how it came to exist. So a capture with
specs contains root CA private keys, machine tokens and the rest, in the clear. This is why the
tracer is a WITH_DEBUG=1 facility aimed at throwaway clusters.

The file is written 0600, but once it is off the node it is just a file. Even without specs
it records resource IDs โ€” node names, cluster membership, affiliate IDs, certificate SANs.
Don't hand a capture around, and don't take one from a cluster you care about.

What is in the file

Newline-delimited JSON, one record per line, in chronological order.

  • {"k":"meta","op":"start"} โ€” header: format version, Talos version, wall clock of t=0.
  • {"k":"graph","op":"dependencies"} โ€” the static controller/resource graph, the same one
    talosctl inspect dependencies renders. Embedding it makes the trace self-contained.
  • {"k":"ctrl",...} โ€” controller lifecycle: start, wake (the controller picked up a
    reconcile event), queue, inputs, stop, crash.
  • {"k":"op",...} โ€” a state operation: get, list, create, update, destroy,
    watch, watchkind, with the resource coordinates, owner, version, phase, finalizers,
    duration and error.
  • {"k":"meta","op":"stop"} โ€” footer: events written and dropped.

t is microseconds since the tracer started. Field names are short on purpose โ€” a boot
produces a lot of these.

How the attribution works

Writes are attributed by the resource owner, but reads are not owned by anybody. To attribute
them, WrapController tags the context it hands to each controller's Run with the controller
name; that tag rides along into every Get/List/Modify the controller makes and is read
back by the state wrapper. Operations with no tag (the API server, the v1alpha1 sequencer) are
recorded for writes only โ€” reads from those would drown out the controllers.

The runtime restarts a failed controller by calling Run again with the same context and
runtime values, and controllers may rely on that identity โ€” internal/pkg/dns.Manager
panics outright if ServeBackground is handed a different context on a restart. So the
wrapper builds the tagged context and the runtime wrapper once per controller and reuses
them; re-tagging per Run breaks that contract, and would also leave a second event relay
behind on every restart, competing with the first for the same upstream channel.

Controller wake-ups are exact: the wrapper relays the reconcile event channel, and an
unbuffered hand-off completes precisely when the controller enters a new reconcile cycle. Where
that cycle ends is not observable from outside the controller, so it is reconstructed offline
from the controller's last state operation before its next wake-up.

Run the package tests with the tag; without it only the stub test runs:

sh
go test -tags sidero.debug ./internal/pkg/ctrltrace/...

Caveats

  • A spec longer than 8 KiB is cut off, and the tail is marked # ... truncated.
  • Events are dropped rather than blocking a controller when the queue (64k) overflows; the
    footer reports how many.
  • Tracing starts before /run is mounted, so early events sit in the queue until the file
    can be opened.
  • With the tracer on, the state wrapper does not implement the optional Teardowner /
    TeardownAndDestroyer fast paths, so teardowns decompose into get + update. This is
    visible in the trace and is the only behavioral difference.

Viewing a trace

hack/ctrltrace-viewer is a single-page viewer for
these files: open its index.html and drop the capture onto it. It draws the controllers
and resource types as a graph clustered by namespace and replays the capture โ€” controllers
flash as they reconcile, resource nodes grow with their live instance count, read and write
pulses travel the edges, and selecting a node shows what reads and writes it.


Overview (`internal/pkg/efivarfs/README.md`)

efivarfs

This package was copied from the upstream repository at efivarfs

There were minimal changes done to comply with the linter and gofmt rules.

The major change was to remove the EFI variable file immutable attribute before writing to it.


Overview (`internal/pkg/msguid/README.md`)

msguid

This package was copied from the upstream repository at msguid

There were minimal changes done to comply with the linter and gofmt rules.


Overview (`internal/app/machined/pkg/controllers/hypervisor/testdata/libvirt/README.md`)

libvirt domain schemas

Generated by make generate from libvirt 12.7.0.
Source archive SHA-256: 7ec1a04e7e4f4069353d4daac117bbe869287f5b202695de61fe1b079efb6cb6 (pinned by siderolabs/extensions/hypervisors/vars.yaml).
The included RNG files form the transitive domain.rng include closure.
COPYING and COPYING.LESSER are the upstream license texts.
Update LIBVIRT_VERSION and LIBVIRT_SHA256 in Makefile together, then run make generate.


--- METRICS ---