Skip to content

Commit 57b45d6

Browse files
committed
Add Dockerfile and shared connectionOptions
1 parent b601ce6 commit 57b45d6

File tree

8 files changed

+107
-8
lines changed

8 files changed

+107
-8
lines changed

production/.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

production/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
lib
22
node_modules
3-
workflow-bundle.js
3+
workflow-bundle.js
4+
certs

production/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# syntax=docker/dockerfile:1
2+
3+
FROM node:16.14.2-bullseye
4+
5+
RUN apt update && apt install -y ca-certificates
6+
7+
ENV NODE_ENV=production
8+
WORKDIR /app
9+
10+
COPY ["package.json", "./"]
11+
RUN npm install --production
12+
13+
ARG TEMPORAL_SERVER="host.docker.internal:7233"
14+
ENV TEMPORAL_SERVER=$TEMPORAL_SERVER
15+
16+
ARG NAMESPACE="default"
17+
ENV NAMESPACE=$NAMESPACE
18+
19+
COPY . .
20+
CMD [ "node", "lib/worker.js" ]

production/README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,21 @@ Hello, Temporal!
2323
### Running this sample in production
2424

2525
1. `npm run build` to build the Worker script and Activities code.
26-
1. `npm run build:workflow` to build the Workflow code bundle.
27-
1. `NODE_ENV=production node lib/worker.js` to run the production Worker.
26+
2. `npm run build:workflow` to build the Workflow code bundle.
27+
3. `NODE_ENV=production node lib/worker.js` to run the production Worker.
28+
29+
If you use Docker in production, replace step 3 with:
30+
31+
```
32+
docker build . --tag my-temporal-worker --build-arg TEMPORAL_SERVER=host.docker.internal:7233
33+
docker run my-temporal-worker
34+
```
35+
36+
### Connecting to deployed Temporal Server
37+
38+
We use [`src/connection.ts`](./src/connection.ts) for connecting to Temporal Server from both the Client and Worker. When connecting to Temporal Server running on our local machine, the defaults (`localhost:7233` for `node lib/worker.js` and `host.docker.internal:7233` for Docker) work. When connecting to a production Temporal Server, we need to:
39+
40+
- Put the TLS certificate in `certs/server.pem`
41+
- Put the TLS private key in `certs/server.key`
42+
- Provide the GRPC endpoint, like `TEMPORAL_SERVER=loren.temporal-dev.tmprl.cloud:7233`
43+
- Provide the namespace, like `NAMESPACE=loren.temporal-dev`

production/src/client.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { Connection, WorkflowClient } from '@temporalio/client';
2+
import { connectionOptions, namespace } from './connection';
23
import { example } from './workflows';
34

45
async function run() {
5-
const connection = new Connection(); // Connect to localhost with default ConnectionOptions.
6-
// In production, pass options to the Connection constructor to configure TLS and other settings.
7-
// This is optional but we leave this here to remind you there is a gRPC connection being established.
6+
const connection = new Connection(connectionOptions);
87

98
const client = new WorkflowClient(connection.service, {
10-
// In production you will likely specify `namespace` here; it is 'default' if omitted
9+
namespace,
1110
});
1211

1312
const result = await client.execute(example, {

production/src/connection.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { readFileSync } from 'fs';
2+
import { fileNotFound } from './errors';
3+
4+
const { TEMPORAL_SERVER, NODE_ENV = 'development', NAMESPACE = 'default' } = process.env;
5+
6+
export { NAMESPACE as namespace };
7+
8+
const isDeployed = ['production', 'staging'].includes(NODE_ENV);
9+
10+
interface ConnectionOptions {
11+
address: string;
12+
tls?: { clientCertPair: { crt: Buffer; key: Buffer } };
13+
}
14+
15+
export const connectionOptions: ConnectionOptions = {
16+
address: TEMPORAL_SERVER || 'localhost:7233',
17+
};
18+
19+
if (isDeployed) {
20+
try {
21+
const crt = readFileSync('./certs/server.pem');
22+
const key = readFileSync('./certs/server.key');
23+
24+
if (crt && key) {
25+
connectionOptions.tls = {
26+
clientCertPair: {
27+
crt,
28+
key,
29+
},
30+
};
31+
}
32+
} catch (e) {
33+
if (!fileNotFound(e)) {
34+
throw e;
35+
}
36+
}
37+
}

production/src/errors.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type ErrorWithCode = {
2+
code: string;
3+
};
4+
5+
function isErrorWithCode(error: unknown): error is ErrorWithCode {
6+
return (
7+
typeof error === 'object' &&
8+
error !== null &&
9+
'code' in error &&
10+
typeof (error as Record<string, unknown>).code === 'string'
11+
);
12+
}
13+
14+
export function getErrorCode(error: unknown) {
15+
if (isErrorWithCode(error)) return error.code;
16+
}
17+
18+
export function fileNotFound(error: unknown) {
19+
return getErrorCode(error) === 'ENOENT';
20+
}

production/src/worker.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Worker } from '@temporalio/worker';
1+
import { NativeConnection, Worker } from '@temporalio/worker';
22
import * as activities from './activities';
3+
import { connectionOptions, namespace } from './connection';
34

45
// @@@SNIPSTART typescript-production-worker
56
const workflowOption = () =>
@@ -8,7 +9,11 @@ const workflowOption = () =>
89
: { workflowsPath: require.resolve('./workflows') };
910

1011
async function run() {
12+
const connection = await NativeConnection.create(connectionOptions);
13+
1114
const worker = await Worker.create({
15+
connection,
16+
namespace,
1217
...workflowOption(),
1318
activities,
1419
taskQueue: 'production-sample',

0 commit comments

Comments
 (0)