# Run a Worker - TypeScript SDK

> Create and run a Temporal Worker using the TypeScript SDK.

This page covers long-lived Workers that you host and run as persistent processes.
For Workers that run on serverless compute like AWS Lambda, see [Serverless Workers](/develop/typescript/workers/serverless-workers).

## Create and run a Worker 

Create a Worker with `Worker.create()`, passing a connection, the Task Queue to poll, and the Workflows and Activities it can execute.
Call `run()` to start polling.

<!--SNIPSTART typescript-hello-worker-->
[hello-world/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/hello-world/src/worker.ts)
```ts
import { NativeConnection, Worker } from '@temporalio/worker';
import * as activities from './activities';

async function run() {
  // Step 1: Establish a connection with Temporal server.
  //
  // Worker code uses `@temporalio/worker.NativeConnection`.
  // (But in your application code it's `@temporalio/client.Connection`.)
  const connection = await NativeConnection.connect({
    address: 'localhost:7233',
    // TLS and gRPC metadata configuration goes here.
  });
  try {
    // Step 2: Register Workflows and Activities with the Worker.
    const worker = await Worker.create({
      connection,
      namespace: 'default',
      taskQueue: 'hello-world',
      // Workflows are registered using a path as they run in a separate JS context.
      workflowsPath: require.resolve('./workflows'),
      activities,
    });

    // Step 3: Start accepting tasks on the `hello-world` queue
    //
    // The worker runs until it encounters an unexpected error or the process receives a shutdown signal registered on
    // the SDK Runtime object.
    //
    // By default, worker logs are written via the Runtime logger to STDERR at INFO level.
    //
    // See https://typescript.temporal.io/api/classes/worker.Runtime#install to customize these defaults.
    await worker.run();
  } finally {
    // Close the connection once the worker has stopped
    await connection.close();
  }
}

run().catch((err) => {
  console.error(err);
  process.exit(1);
});
```
<!--SNIPEND-->

Workflows are registered by path rather than by value, because they run in a separate JavaScript context.

## Register Workflows and Activities 

All Workers polling the same Task Queue must register the same Workflow Types and Activity Types.
A Task Queue does not route by type, so any Worker polling it can receive any Task on that queue.
A Worker that receives a Task for a type it did not register fails that Task.

In development, use [`workflowsPath`](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions/#workflowspath):

<!--SNIPSTART typescript-worker-create -->
[snippets/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/snippets/src/worker.ts)
```ts
import { Worker } from '@temporalio/worker';
import * as activities from './activities';

async function run() {
  const worker = await Worker.create({
    workflowsPath: require.resolve('./workflows'),
    taskQueue: 'snippets',
    activities,
  });

  await worker.run();
}
```
<!--SNIPEND-->

In this snippet, the Worker bundles the Workflow code at runtime.

In production, you can improve your Worker's startup time by bundling in advance. As part of your production build, call `bundleWorkflowCode`:

<!--SNIPSTART typescript-bundle-workflow -->
[production/src/scripts/build-workflow-bundle.ts](https://github.com/temporalio/samples-typescript/blob/main/production/src/scripts/build-workflow-bundle.ts)
```ts
import { bundleWorkflowCode } from '@temporalio/worker';
import { writeFile } from 'fs/promises';
import path from 'path';

async function bundle() {
  const { code } = await bundleWorkflowCode({
    workflowsPath: require.resolve('../workflows'),
  });
  const codePath = path.join(__dirname, '../../workflow-bundle.js');

  await writeFile(codePath, code);
  console.log(`Bundle written to ${codePath}`);
}
```
<!--SNIPEND-->

Then pass the bundle to the Worker:

<!--SNIPSTART typescript-production-worker-->
[production/src/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/production/src/worker.ts)
```ts
const workflowOption = () =>
  process.env.NODE_ENV === 'production'
    ? {
        workflowBundle: {
          codePath: require.resolve('../workflow-bundle.js'),
        },
      }
    : { workflowsPath: require.resolve('./workflows') };

async function run() {
  const worker = await Worker.create({
    ...workflowOption(),
    activities,
    taskQueue: 'production-sample',
  });

  await worker.run();
}
```
<!--SNIPEND-->

## Connect to Temporal Cloud 

To run a Worker against Temporal Cloud, configure the connection with your Namespace address and authentication credentials.
See [Connect to Temporal Cloud](/develop/typescript/client/temporal-client#connect-to-temporal-cloud) for setup instructions.

## Configure Worker options 

[`Worker.create()`](https://typescript.temporal.io/api/classes/worker.Worker#create) accepts options that control concurrency limits, pollers, timeouts, and caching.
The defaults work for most cases.

To tune these values against real load, see [Worker performance](/develop/worker-performance) and the [Worker tuning reference](/develop/worker-tuning-reference).

## Run a versioned Worker 

Set a Worker Deployment Version and enable versioning in `workerDeploymentOptions`.
When `useWorkerVersioning` is `true`, `defaultVersioningBehavior` is required.

<!--SNIPSTART typescript-versioned-worker-->
[features/snippets/worker/worker.ts](https://github.com/temporalio/features/blob/main/features/snippets/worker/worker.ts)
```ts
const worker = await Worker.create({
  connection,
  taskQueue: 'my-task-queue',
  workflowsPath: require.resolve('./workflows'),
  workerDeploymentOptions: {
    version: { deploymentName: 'my-app', buildId: '1.0' },
    useWorkerVersioning: true,
    defaultVersioningBehavior: 'PINNED',
  },
});
```
<!--SNIPEND-->

`defaultVersioningBehavior` covers every Workflow on the Worker.
To set the behavior for a single Workflow instead, pass `versioningBehavior` through [`setWorkflowOptions()`](https://typescript.temporal.io/api/namespaces/workflow#setworkflowoptions) where you define that Workflow.

See [Worker Versioning](/worker-versioning) for the available versioning behaviors and how new versions roll out.

## Shut down a Worker 

Workers shut down when the process receives any of the operating system signals listed in
[shutdownSignals](https://typescript.temporal.io/api/interfaces/worker.RuntimeOptions#shutdownsignals): `'SIGINT'`,
`'SIGTERM'`, `'SIGQUIT'`, and `'SIGUSR2'`.

In development, shut down Workers with `Ctrl+C` (`SIGINT`) or
[nodemon](https://github.com/temporalio/samples-typescript/blob/c37bae3ea235d1b6956fcbe805478aa46af973ce/hello-world/package.json#L10)
(`SIGUSR2`). In production, give Workers time to finish in-progress Activities by setting
[shutdownGraceTime](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#shutdowngracetime):

<!--SNIPSTART typescript-worker-graceful-shutdown-->
[features/snippets/worker/worker.ts](https://github.com/temporalio/features/blob/main/features/snippets/worker/worker.ts)
```ts
const worker = await Worker.create({
  connection,
  taskQueue: 'my-task-queue',
  workflowsPath: require.resolve('./workflows'),
  shutdownGraceTime: '30s',
});

await worker.run();
```
<!--SNIPEND-->

As soon as a Worker receives one of those signals or a shutdown request, the Worker stops polling for new Tasks and allows in-flight
Tasks to complete until `shutdownGraceTime` is reached. Any Activities still running at that time stop running and are
rescheduled by the Temporal Service when an Activity timeout occurs.

If you must guarantee that the Worker eventually shuts down, set
[shutdownForceTime](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#shutdownforcetime).

In integration tests or when automating a fleet of Workers, shut a Worker down programmatically with
[Worker.shutdown()](https://typescript.temporal.io/api/classes/worker.Worker#shutdown).

See [Worker shutdown](/encyclopedia/workers/worker-shutdown) for what happens to in-flight Workflow Tasks and Activities.

### Worker states

At any time, you can Query Worker state with
[Worker.getState()](https://typescript.temporal.io/api/classes/worker.Worker#getstate). A Worker is always in one of
seven states:

- `INITIALIZED`: The initial state of the Worker after calling
  [Worker.create()](https://typescript.temporal.io/api/classes/worker.Worker#create) and successfully connecting to the
  server.
- `RUNNING`: [Worker.run()](https://typescript.temporal.io/api/classes/worker.Worker#run) was called and the Worker is
  polling Task Queues.
- `FAILED`: The Worker encountered an unrecoverable error; `Worker.run()` should reject with the error.
- The last four states are related to the Worker shutdown process:
  - `STOPPING`: The Worker received a shutdown signal or `Worker.shutdown()` was called. The Worker forcefully shuts
    down after `shutdownGraceTime` expires.
  - `DRAINING`: All Workflow Tasks have been drained; waiting for Activities and cached Workflows eviction.
  - `DRAINED`: All Activities and Workflows have completed; ready to shut down.
  - `STOPPED`: Shutdown complete; `worker.run()` resolves.

If you need more visibility into internal Worker state, see the
[Worker class](https://typescript.temporal.io/api/classes/worker.Worker) in the API reference.

## Run a Worker on Docker 

> **📝 Note:**
>
> To improve Worker startup time, prepare Workflow bundles ahead of time. See the
> [production sample](https://github.com/temporalio/samples-typescript/tree/main/production) for details.
>

Workers based on the TypeScript SDK can be deployed and run as Docker containers.

Use an LTS Node.js release such as 18, 20, 22, or 24. Both `amd64` and `arm64` architectures are supported. A
glibc-based image is required; musl-based images are _not_ supported (see below).

The most direct way to deploy a TypeScript SDK Worker on Docker is to start with the `node:20-bullseye` image. For example:

```dockerfile
FROM node:20-bullseye

# For better cache utilization, copy package.json and lock file first and install the dependencies before copying the
# rest of the application and building.
COPY . /app
WORKDIR /app

# Alternatively, run npm ci, which installs only dependencies specified in the lock file and is generally faster.
RUN npm install --only=production \
    && npm run build

CMD ["npm", "start"]
```

For smaller images and/or more secure deployments, it is also possible to use `-slim` Docker image variants (like
`node:20-bullseye-slim`) or `distroless/nodejs` Docker images (like `gcr.io/distroless/nodejs20-debian11`) with the
following caveats.

### Using `node:slim` images

`node:slim` images leave out common packages found in regular images, which makes them much smaller.

One of the packages they leave out is `ca-certificates`, and the TypeScript SDK requires those root TLS certificates.
The requirement holds even when you connect to a local Temporal Service, and when the connection config does not use
TLS. Install the package when you build the image:

```dockerfile
FROM node:20-bullseye-slim

RUN apt-get update \
    && apt-get install -y ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# ... same as with regular image
```

Failure to install this dependency results in a `[TransportError: transport error]` runtime error, because the
certificates cannot be verified.

### Using `distroless/nodejs` images

`distroless/nodejs` images include only the files required to execute `node`. They are about half the size of
`node:slim` images and carry a smaller attack surface.

TypeScript SDK Workers run on `distroless/nodejs` images, as long as your own code does not need dependencies that those
images leave out.

Build tools, including the `npm` command, are _not_ in the `distroless/nodejs` image, so building inside it fails.
Use a multi-step Dockerfile instead:

```dockerfile
# -- BUILD STEP --

FROM node:20-bullseye AS builder

COPY . /app
WORKDIR /app

RUN npm install --only=production \
    && npm run build

# -- RESULTING IMAGE --

FROM gcr.io/distroless/nodejs20-debian11

COPY --from=builder /app /app
WORKDIR /app

CMD ["node", "build/worker.js"]
```

### Properly configure Node.js memory in Docker

By default, `node` sets its maximum old-gen memory to 25% of the machine's _physical memory_, up to 4 GB. In Docker,
that reads the host's memory rather than the container's limit. The container then either underuses the memory it was
given, or `node` tries to exceed that limit and the operating system kills the process.

Set the `--max-old-space-size` `node` argument explicitly, to roughly 80% of the memory in megabytes you want the `node`
process to use. Tune the value against your own application.

Pass this argument through the [`NODE_OPTIONS` environment variable](https://nodejs.org/api/cli.html#node_optionsoptions).

### Do not use Alpine

Alpine replaces glibc with musl, which is incompatible with the Rust core of the TypeScript SDK. Errors like these point
to a musl-based image:

```sh
Error: Error loading shared library ld-linux-x86-64.so.2: No such file or directory (needed by /opt/app/node_modules/@temporalio/core-bridge/index.node)
```

Or like this:

```sh
Error: Error relocating /opt/app/node_modules/@temporalio/core-bridge/index.node: __register_atfork: symbol not found
```
