> ## 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.

# Deploy a microservice

> Build and ship each microservice as its own independently deployable Bun image from the Dockerfile generated by microservice:create — one image per service, its own port, its own config.

Every microservice created with [`microservice:create`](/cli/commands/microservice-create) gets **its own `Dockerfile`** so it builds, ships, and scales independently of the API and of every other service. A microservice is a standalone process: its own port, its own datastores, reached by the API over HTTP at a URL resolved from configuration (see [Microservice networking](/microservice/networking)). Deploying one is the same container workflow as the [API](/deployment/api) — build, push, run — but scoped to a single module.

## The generated Dockerfile

`microservice:create` copies the app module's [multi-stage Bun Dockerfile](/deployment/api#the-dockerfile) into the microservice folder and rewrites it for that service:

* `{{NAME}}` becomes the microservice's snake-case name (used in image tags and comments).
* `modules/app/` paths become `modules/<kebab-name>/`, so the Dockerfile lives at and refers to the microservice's own folder.

The result is a `modules/<kebab-name>/Dockerfile` with the same five stages as the API image — `base`, `deps`, `prod-deps`, `build`, `production` — producing a compiled `dist` bundle plus production `node_modules` plus the runtime YAML the framework reads at startup. Everything in [The Dockerfile](/deployment/api#the-dockerfile) on the API page applies here too; only the paths and the image name differ.

<Note>
  Like the API image, the build context **must be the repository root**. The
  Dockerfile installs from the root `package.json`/`bun.lock` and reads shared
  runtime files by repo-relative paths — a microservice image is still built
  from the top of the monorepo, just pointed at a different `Dockerfile`.
</Note>

## Build the image

Point `-f` at the microservice's own Dockerfile and build the `production` target from the repo root. For a service named `billing`:

```bash theme={null}
docker build \
  -f modules/billing/Dockerfile \
  --target production \
  -t billing:latest \
  .
```

The `production` stage behaves exactly as the API's: it binds `HOST_NAME=0.0.0.0`, listens on `PORT` (image default `3500`, override per environment), runs as the non-root `bun` user, and ships a `HEALTHCHECK` that polls `/healthcheck`.

<Tip>
  Each microservice is a separate image with a separate tag. Give each its own
  repository in the registry (`.../billing`, `.../notifications`) and its own
  SHA-tagged builds, so services version and roll back independently.
</Tip>

## Push to a container registry

Tag for your registry and push, exactly as for the API — one repository per service:

<Tabs>
  <Tab title="GitHub (GHCR)">
    ```bash theme={null}
    echo "$GITHUB_TOKEN" | docker login ghcr.io -u <username> --password-stdin

    docker tag billing:latest ghcr.io/<owner>/billing:latest
    docker push ghcr.io/<owner>/billing:latest
    ```
  </Tab>

  <Tab title="Docker Hub">
    ```bash theme={null}
    docker login -u <username>

    docker tag billing:latest <username>/billing:latest
    docker push <username>/billing:latest
    ```
  </Tab>

  <Tab title="AWS ECR">
    ```bash theme={null}
    aws ecr create-repository --repository-name billing
    aws ecr get-login-password --region <region> \
      | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com

    docker tag billing:latest <account>.dkr.ecr.<region>.amazonaws.com/billing:latest
    docker push <account>.dkr.ecr.<region>.amazonaws.com/billing:latest
    ```
  </Tab>

  <Tab title="Google Artifact Registry">
    ```bash theme={null}
    gcloud auth configure-docker <region>-docker.pkg.dev

    docker tag billing:latest <region>-docker.pkg.dev/<project>/services/billing:latest
    docker push <region>-docker.pkg.dev/<project>/services/billing:latest
    ```
  </Tab>
</Tabs>

The [API deployment page](/deployment/api#push-to-a-container-registry) covers registry authentication and immutable SHA tagging in more depth — the same rules apply to every service.

## Run it in production

Run each microservice as its own container, on its own port, pointed at its own datastores through the environment. A per-service Compose block on a single host:

```yaml docker-compose.production.yml theme={null}
services:
  billing:
    image: ${BILLING_IMAGE:-ghcr.io/owner/billing:latest}
    restart: unless-stopped
    ports:
      - "3001:3001"
    env_file: ./billing.env
    environment:
      - HOST_NAME=0.0.0.0
      - PORT=3001
```

```bash theme={null}
docker pull ghcr.io/<owner>/billing:latest
BILLING_IMAGE=ghcr.io/<owner>/billing:latest docker compose \
  -f docker-compose.production.yml up -d --no-deps --wait billing
```

The `--wait` flag blocks until the container reports healthy through the Dockerfile `HEALTHCHECK`. The same image runs on Kubernetes, ECS/Fargate, Cloud Run, Fly.io, and the rest — treat each microservice as its own deployable unit with its own scaling policy.

<Warning>
  Each service owns its data. Give every microservice its **own** Postgres and
  Redis rather than pointing several services at one database for shared state —
  services talk to each other over HTTP, not a shared schema. See
  [Microservice networking](/microservice/networking).
</Warning>

## Configure the environment

A microservice reads the same flattened env vars as the API, from its own `modules/<kebab-name>/.env.yml` in development and from injected secrets in production. Set at minimum:

| Variable                                                      | Purpose                                                                   |
| ------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `APP_ENV`                                                     | Environment name.                                                         |
| `HOST_NAME`                                                   | `0.0.0.0` inside a container.                                             |
| `PORT`                                                        | The service's distinct port (assigned from `3001` upward at create time). |
| `DATABASE_URL`                                                | The service's **own** Postgres.                                           |
| `CACHE_REDIS_URL`, `PUBSUB_REDIS_URL`, `RATE_LIMIT_REDIS_URL` | The service's **own** Redis endpoints.                                    |
| `CORS_*`                                                      | Only if browsers call the service directly (see below).                   |

### Service discovery: wire the URL back to the API

Deploying the container is only half the job — the API has to be able to reach it. Discovery is env-var-driven and per-environment:

1. `modules/app/app.yml` declares the service and names the env var that holds its URL — `MICROSERVICE_<SNAKE_UPPER>_URL` (e.g. `MICROSERVICE_BILLING_URL`).
2. In each environment, set that variable to where the service is actually reachable — `https://billing.internal.example.com` in production, `http://localhost:3001` locally.

So after you deploy the `billing` image, set `MICROSERVICE_BILLING_URL` in the **API's** production environment to the deployed service's address. Never hard-code the hostname in source — the API always resolves it from configuration. Full details in [Microservice networking](/microservice/networking).

<Note>
  Prefer a **private network address** (internal load balancer, service mesh, or
  private DNS) for service-to-service URLs so internal traffic never leaves your
  network. Only expose a microservice publicly if a browser or third party must
  reach it directly.
</Note>

### CORS

A microservice usually sits behind the API and is called server-to-server, so it needs **no** CORS configuration — CORS is a browser mechanism. Configure `CORS_*` on a microservice only when a browser calls it directly; then apply the same rules as the [API's CORS setup](/security/cors): an explicit `CORS_ORIGINS` allowlist, never `*` with credentials.

## Deploy with CI/CD

The pattern mirrors the [API pipeline](/deployment/api#deploy-with-cicd): build the service's image, push it under its own repository tag, then pull-and-swap on the target. Give each microservice its own build/deploy job (or a matrix entry) keyed on its `modules/<kebab-name>/Dockerfile`, and build only the services whose files changed so unrelated services aren't rebuilt on every commit.

## Checklist

* Build the `production` target with `-f modules/<name>/Dockerfile` and the **repo root** as context.
* Push each service to its **own** registry repository with immutable SHA tags.
* Run each service on its **own** port with its **own** Postgres and Redis.
* After deploy, set `MICROSERVICE_<NAME>_URL` in the **API's** environment to the service's address.
* Keep service-to-service traffic on a **private** network where possible.
* Add CORS only if a browser calls the service directly.

## Related

* [Deploy an API](/deployment/api) — the shared image model and registry workflow in full.
* [Microservice networking](/microservice/networking) — ports, service declaration, and per-environment URLs.
* [Microservice overview](/microservice/overview) — when to reach for a microservice.
* [microservice:create](/cli/commands/microservice-create) — the generator that writes the Dockerfile and wires discovery.
* [Configuration](/getting-started/configuration) — how `.env.yml` maps to injected env vars.
* [CORS](/security/cors) — cross-origin config, if the service is browser-facing.
