> ## Documentation Index
> Fetch the complete documentation index at: https://docs.talosjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish to Docker Hub

> Build and push container images for every package and module that ships a Dockerfile — driven end to end by the Talos CLI, so one command authenticates once and containerizes the whole workspace.

Where [Publish to npm](/deployment/npm) ships **libraries**, publishing to Docker ships **images**: the packages under `packages/` and modules under `modules/` that carry a `Dockerfile` and run as containers. [Deploy an API](/deployment/api) walks through building the app module's image by hand with `docker build`/`docker push`; [`docker:publish`](/cli/commands/docker-publish) automates that across the workspace — save a registry token once, then let one command log in, build each target that has a `Dockerfile`, tag it, and push it to Docker Hub.

## What gets published

Every directory under `packages/` and `modules/` is a candidate, but only those that ship a `Dockerfile` are built — the rest are reported as *ignored*. The image is tagged `<username>/<name>:<tag>`, where `<name>` is the directory name and `<username>` comes from your saved credentials.

| Source   | Location           | Built when                           |
| -------- | ------------------ | ------------------------------------ |
| Packages | `packages/<name>/` | `packages/<name>/Dockerfile` exists. |
| Modules  | `modules/<name>/`  | `modules/<name>/Dockerfile` exists.  |

<Note>
  The build context is the target directory and the image is built from its
  `Dockerfile` (`docker build .`). Make sure that `Dockerfile` and its
  `.dockerignore` reference paths relative to that directory — see
  [Deploy an API](/deployment/api) for the app module's multi-stage example.
</Note>

## Step 1 — Save a Docker token

Publishing authenticates with a Docker **access token**. Save it once with [`docker:credentials:create`](/cli/commands/docker-credentials-create); the registry, username, and token are written as block-style YAML to `~/.talos/credentials/docker.yml` under the `default` profile and reused by every publish.

```bash theme={null}
talos docker:credentials:create
```

You are prompted for the registry (defaults to `docker.io`), username, and token (input hidden), or pass them non-interactively for CI:

```bash theme={null}
talos docker:credentials:create --registry=docker.io --username=ooneex --token=dckr_pat_xxxxxxxx --silent
```

<Tip>
  Create the token at
  [app.docker.com → Personal access tokens](https://app.docker.com/settings/personal-access-tokens/create).
  Give it **Read & Write** scope so it can push images, and the shortest expiry
  your release cadence allows.
</Tip>

<Warning>
  The token is stored in plaintext under `~/.talos/credentials/`. Keep that
  directory off shared machines and out of any backup that leaves your control.
  In CI, inject the token via `--token` from a secret store rather than
  committing `docker.yml`.
</Warning>

## Step 2 — Publish

With credentials saved, build and push every container in the workspace:

```bash theme={null}
talos docker:publish
```

Or target specific packages and modules by name (comma-separated):

```bash theme={null}
talos docker:publish --packages=gateway,worker
talos docker:publish --modules=billing,catalog
```

By default the tag is the target's `package.json` version, falling back to `latest`. Override it explicitly:

```bash theme={null}
talos docker:publish --modules=billing --tag=edge
```

### Options

| Option       | Description                                                   | Default                               |
| ------------ | ------------------------------------------------------------- | ------------------------------------- |
| `--packages` | Comma-separated package names to publish (under `packages/`). | All packages and modules              |
| `--modules`  | Comma-separated module names to publish (under `modules/`).   | All packages and modules              |
| `--tag`      | Image tag to build and push.                                  | `package.json` version, else `latest` |
| `--silent`   | Suppress log output and the publishing spinner (use in CI).   | `false`                               |

### What the command does

`docker:publish` runs once up front, then per target:

1. **Logs in** to the registry with `docker login`, feeding the saved token through stdin so it never appears in the process arguments. A failed login aborts the whole run.
2. For each resolved target that ships a `Dockerfile`, **builds** the image with `docker build -t <username>/<name>:<tag> .` from the target directory.
3. **Pushes** it with `docker push`. A build failure skips the push for that target and is reported.
4. Skips targets with **no `Dockerfile`** — discovered targets are silently counted as ignored; an explicitly named target without one is reported as an error.

At the end it prints a summary — `N published, M ignored`.

<Note>
  For any registry other than `docker.io`, the image reference is prefixed with
  the registry host (for example `ghcr.io/ooneex/gateway:1.2.0`), so the same
  command targets Docker Hub or a private registry depending on the `registry`
  you saved in Step 1.
</Note>

## Publish from CI

The same commands run unattended. Provide the token from a secret and add
`--silent` to keep logs clean:

<Tabs>
  <Tab title="GitHub Actions">
    ```yaml .github/workflows/docker.yml theme={null}
    name: docker
    on:
      push:
        tags: ["*@*"]        # matches release:create tags, e.g. @talosjs/gateway@1.2.0
    jobs:
      publish:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: oven-sh/setup-bun@v2
          - run: bun install
          - run: >
              bunx talos docker:credentials:create
              --registry=docker.io --username="$DOCKER_USERNAME"
              --token="$DOCKER_TOKEN" --silent
            env:
              DOCKER_USERNAME: ${{ vars.DOCKER_USERNAME }}
              DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
          - run: bunx talos docker:publish --silent
    ```
  </Tab>

  <Tab title="Generic CI">
    ```bash theme={null}
    bun install

    talos docker:credentials:create \
      --registry=docker.io --username="$DOCKER_USERNAME" \
      --token="$DOCKER_TOKEN" --silent
    talos docker:publish --silent
    ```

    `$DOCKER_TOKEN` comes from your CI secret store; never commit `~/.talos/credentials/docker.yml`.
  </Tab>
</Tabs>

<Warning>
  The CI runner needs a working Docker daemon (available on GitHub's
  `ubuntu-latest`). Give the token **Read & Write** scope on only the
  repositories you push, and prefer a short expiry.
</Warning>

## Checklist

* Save the Docker token once with `docker:credentials:create` (or inject it via `--token` in CI).
* Add a `Dockerfile` to each package/module you want containerized — targets without one are skipped.
* Let the tag default to the `package.json` version, or pass `--tag` for a named tag such as `edge`.
* Ensure `docker` is on `PATH` and the daemon is running before publishing.
* In CI, add `--silent` and pull the token from a secret store — never commit `docker.yml`.

## Related

* [docker:publish](/cli/commands/docker-publish) — the publish command and all its options.
* [docker:credentials:create](/cli/commands/docker-credentials-create) — save the Docker registry token.
* [Deploy an API](/deployment/api) — build and run the app module's container image by hand.
* [Publish to npm](/deployment/npm) — ship packages and modules as libraries instead of images.
* [Monorepo](/getting-started/monorepo) — how packages and modules are laid out.
