# docker/docs: the static site repository behind docs.docker.com

> The source of the Docker documentation is a Hugo site with a Go module, a Node build chain and a Vale style pipeline. Here is how the pieces fit, and what contributing actually involves.

**docker/docs** — Source repo for Docker's Documentation

- Repository: https://github.com/docker/docs
- Website: https://docs.docker.com
- Stars: 4,666 · Forks: 8,505
- Language: Markdown
- License: Apache-2.0
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/docker-docs

## What the repository actually contains

The README calls this the source for the Docker Docs Website and that is accurate without much qualification. Everything under `content/` is Markdown that Hugo turns into pages, `layouts/` holds the templates, `static/` and `assets/` carry images and styles, and `i18n/` holds translations. There is no application code here and no Docker engine, so nothing in this tree will teach you how containers work. It is a publishing pipeline.

What makes the tree more than a folder of Markdown is the tooling wrapped around it. A `go.mod` pulls in the projects whose command reference pages get vendored into the docs, including buildx, the Docker CLI, Compose, model-runner and the Moby API. `package.json` carries the front end half: Hugo's Tailwind pipeline, Alpine.js for the interactive bits, highlight.js, marked and mermaid. Both halves exist because the docs quote command syntax and API shapes from upstream repositories and need to regenerate those quotes rather than retype them.

There are no GitHub releases in this repository, which is correct for a docs site. Version numbers live in the vendored upstream projects and in the file paths for versioned content, not in tags here. The repository is not archived and the last push was on 2026-09-22 on the `main` branch.

## Running the site locally on port 1313

The shortest path to a local copy is the compose file at the root, which builds the `build-base` image from the Dockerfile, publishes port 1313, and starts Hugo bound to all interfaces.

```yaml
services:
  server:
    build:
      context: .
      target: build-base
    ports:
      - "1313:1313"
    entrypoint: ["hugo", "server", "--bind", "0.0.0.0"]
```

The interesting part is the watch configuration below it. A generic sync action copies the working tree into the container, but it explicitly ignores `hack/api-docs/` and the four vendored reference files under `content/reference/api/`. Each of those ignored paths has a matching rebuild action, which tells Compose to rebuild the image instead of restarting the container. That distinction exists because regenerating the API reference takes far longer than re-rendering Markdown, and a plain file sync would give you a stale reference page with no indication why.

The entrypoint override is what makes this a preview server rather than a one-shot build. Hugo watches the mounted content and reloads on change, so a contributor edits a page and sees the result without running anything else. The tradeoff is that this is a preview path, not a replica of production: the production build runs with `--minify` and `--panicOnWarning`, which means a warning that is harmless in the preview server becomes a build failure on Netlify.

## The multi-stage image and the pinned tool versions

The Dockerfile declares its toolchain as build arguments at the top, which is the clearest statement of what a contributor needs installed.

```bash
ARG ALPINE_VERSION=3.23
ARG GO_VERSION=1.26
ARG HTMLTEST_VERSION=0.17.0
ARG VALE_VERSION=3.17.0
ARG HUGO_VERSION=0.163.0
ARG NODE_VERSION=24
ARG PAGEFIND_VERSION=1.5.2
```

Every one of those numbers is pinned to an exact patch release, including `HTMLTEST_VERSION` and `PAGEFIND_VERSION`, which are for link checking and search indexing rather than for building pages. Pinning Vale and htmltest matters more than it looks: a style linter that changes its rules between releases would make a pull request fail for a reason that has nothing to do with the edit.

The stages divide cleanly. A `base` stage installs bash, git, nodejs, npm, gcompat and rsync on Alpine. An `npm` stage runs `npm ci` with a cache mount for `/root/.npm`, so a rebuild after a dependency change is fast but the install is reproducible from `package-lock.json`. A separate `hugo` stage downloads the extended Hugo binary for the target architecture rather than using the Alpine package, which is why the Dockerfile tracks `TARGETARCH`.

The `build-base` stage is the one the compose file targets, and it does something before you ever start Hugo:

```bash
RUN --mount=type=cache,target=/root/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    ./hack/api-docs/run.sh test && ./hack/api-docs/run.sh generate
```

That script has a test mode and a generate mode, run in that order. Every local build therefore validates the vendored API extraction before rendering a single page, which is the right order: you do not want to debug a rendering error that was really a stale reference file.

## Where the CLI and API reference pages come from

The `go.mod` file explains its own purpose in a comment: Hugo vendors documentation from upstream repositories, and the require block pins the version of each one. Eight projects are listed, and they are the projects Docker users actually type commands into.

```bash
require (
	github.com/docker/buildx v0.37.1
	github.com/docker/cli v29.7.2+incompatible
	github.com/docker/compose/v5 v5.5.1
	github.com/docker/docker-agent v1.126.0
	github.com/docker/model-runner v1.1.36
	github.com/moby/buildkit v0.33.0
	github.com/moby/moby/api v1.56.0
)
```

The mechanism this enables is worth understanding before you edit a reference page by hand. A file like `content/reference/api/hub/latest.yaml` holds an API description extracted from an upstream repository, not written by a doc author. Running `hack/api-docs/run.sh generate` refreshes it from the pinned Go module, which means flag names and field types come from the source of truth rather than from a person. Hand edits to those files are not just discouraged, they are the next thing to be overwritten.

The `tool` block below the require block is a Go 1.24 style tool directive listing the same projects plus `scout-cli`. Its presence means the upstream repositories can be invoked as part of the docs build without a separate install step. For a contributor this matters in a practical way: if a command reference page disagrees with the current CLI, the fix is to bump the module version and regenerate, not to rewrite the table by memory.

The versioned paths under `content/reference/api/` also explain why the compose watch rules single them out. They change when a module is bumped, and they change through generation rather than editing.

## The style gates a pull request has to clear

Three of the files in the tree exist purely to catch mistakes before review: `.vale.ini` with its styles in `_vale/` and its template at `vale-rdjsonl.tmpl`, `.htmltest.yml` for link checking, and `.rumdl.toml` alongside `.prettierrc` and `.prettierignore` for Markdown and formatting rules. The Vale version is pinned in the Dockerfile, and the rdjsonl template means results are read as a JSON lines stream rather than prose.

This is a stricter setup than most documentation repositories use, and the reason is visible in the README. Docker's docs are one of the most heavily read technical documentation sets in the industry, and a broken link or a banned construction has real cost. The trade-off is contributor friction: a first-time author will hit Vale rules that read like style police, and the README's own guidance is to fix problems in reverse order, starting with the structural complaints.

The `rumdl` entry in the dev dependencies is a Markdown linter, and `prettier-plugin-go-template` exists because Go templates inside Markdown need formatting that Prettier does not handle natively. That single dependency line is a decent summary of the project's shape: the repository has to format prose, Hugo templates and Go template syntax inside the same files, and each of those wants different handling.

There are also agent instructions in the tree at `AGENTS.md` and `CLAUDE.md`, plus a `.agents/` directory and `.claude` at the root, alongside `docs_engineer.yml` and `tech_writer.yml`. The README does not explain what these contain, which makes them one of the places where you have to read the file rather than infer from the listing.

## Feedback paths and where the README stops

The README is short and mostly administrative. It lists three ways to give feedback: open a new issue on the docs repository, use the Request changes control in the right column of every page on docs.docker.com, or use the Give feedback control on each page. Community support goes to Docker Community Slack, and the README is explicit that personalised support is a paid feature available through the Pro, Team and Business subscriptions.

That sentence is worth pausing on, because it explains an architectural fact. The docs site is a product funnel, not a neutral mirror. A reader who opens the Playground or wants a guided tutorial lands in a path that ends at a pricing page, and the repository tree supports that story: `layouts/` and `static/` carry the interactive components, `@docker/marlin-sdk-web-public` in `package.json` supplies an embedded SDK, and `pagefind.yml` and the Pagefind build argument point at client side search indexing. An Apache 2.0 licence covers the repository, and the copyright line runs 2013 to 2026.

The README then stops. It points to three files for contributors and nothing else, so the practical detail of the workflow lives in `CONTRIBUTING.md`, the writing rules live in `STYLE.md`, and the component and shortcode reference lives in `COMPONENTS.md`. The README does not document how a preview is deployed, how the Netlify build is triggered, or what `netlify.toml` configures, and it does not describe the `archetypes/` scaffolding used for new pages. For a repository that produces one of the busiest documentation sites on the web, that thinness is the main thing a reader should know before deciding how much to expect from the README itself.

## Conclusion

docker/docs makes sense for one narrow job: changing the words and structure on docs.docker.com. It is a marketing site as much as a documentation site, with search, a paid support tier and interactive playground components, so it is not a good template for a lean product manual. Start at CONTRIBUTING.md and STYLE.md, run the site through compose.yaml on port 1313, and check a change against the Vale rules in _vale/ before opening a pull request. If your goal is learning Docker itself rather than editing the site, the rendered pages are the better entry point. The last push was on 2026-09-22, so the tree you read matches the current site closely.

## FAQ

### How do I run the Docker documentation site locally?

The root compose file is the documented path: it builds the build-base image from the Dockerfile, publishes port 1313, and starts Hugo with the server subcommand bound to 0.0.0.0. Its watch configuration syncs your working tree into the container but rebuilds instead for the API reference files, since those are generated by hack/api-docs/run.sh.

### What is the Go module in the docker/docs repository for?

It exists so Hugo can vendor documentation from upstream repositories. The require block pins buildx, the Docker CLI, Compose, docker-agent, model-runner, BuildKit and the Moby API, and the tool block exposes those repositories to the docs build. Bumping a version there regenerates the API reference pages rather than having an author retype command syntax.

### Does the docker/docs repository contain the Docker engine source code?

No. This repository is the Hugo source for docs.docker.com, with content, layouts, styles and tooling. The engine and the CLI live in moby/moby and docker/cli, and this repository only imports their API descriptions so the reference pages stay accurate.

### What style and link checks run on a documentation pull request?

Vale runs with the configuration in .vale.ini, its styles in _vale/ and its rdjsonl template, htmltest checks links using .htmltest.yml, and rumdl plus prettier handle Markdown formatting. Vale and htmltest are pinned to exact versions in the Dockerfile, which is what keeps a lint failure traceable to your edit.

## Sources

- [docker/docs on GitHub](https://github.com/docker/docs)
- [Issues](https://github.com/docker/docs/issues)
- [License: Apache-2.0](https://github.com/docker/docs/blob/main/LICENSE)
- [Project website](https://docs.docker.com)
- [README](https://github.com/docker/docs/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/docker-docs
