Docker + Node (Typescript)
The goal of this example is to show you how to get a Node.js with Typescript application into a Docker container and make a docker-compose to develop environment. I will try to explain how to pass from develop to production with docker. So I'll assume you know what is docker and you have it installed.
As an example, let's create a simple web server with Express, which you can replace with your favorite node package.
Getting node dependencies
First, create a new directory where all the files would live. In this directory run the next command to create a new node package.
npm init -y
Now we need to install some dependencies to build Typescript-Node app
npm install -D nodemon ts-node typescript @types/express @types/node
npm install -S express
Then we concat the next npm scripts to package.json
...
"scripts": {
"build": "tsc",
"start": "nodemon src/index.ts"
},
...
Typescript needs a json file with basic settings to compiler, you can see examples here
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"outDir": "build"
}
}
This configuration sets:
target
Javascript version toes5
(ECMAScript 5).modules
handling tocommonjs
.sourceMap
totrue
because it helps to track errors from production code (JS) to development code (TS).outdir
tobuild
which means that our compiled code will be stored in thebuild
directory.
As you can see we use nodemon, so we need to set up the configuration file just like that
{
"verbose": true,
"ignore": ["src/**/*.spec.ts"],
"watch": ["src/**/*.ts"],
"execMap": {
"ts": "node --inspect=0.0.0.0:9229 --nolazy -r ts-node/register"
}
}
- Well the
verbose
is to indicates to nodemon that you have a descriptive log of what it's happening. ignore
is an array of files to ignore by watcher.watcher
is an array with files that nodemon must watch.- Finally,
execMap
is an object that defines what interpreter executes which files (now nodemon will know what use to run ts files).
Making example application
To make an application with express we will create two files:
- First the router named
example.router.ts
:
import { Router, Request, Response, NextFunction } from "express";
const router = Router();
router.get(
"/hello/:name",
(req: Request, res: Response, next: NextFunction) => {
const { name } = req.params;
res.send(`Hello ${name} from express`);
},
);
export const exampleRouter = router;
- And the entry point of app,
index.ts
:
import * as express from "express";
import { exampleRouter } from "./routes/example.router";
const PORT = 8080;
let app = express();
app.use(exampleRouter);
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Preparing develop environment with docker-compose
Now we will build our develop environment, we will make the next Dockerfile.dev
:
FROM node:10.15.3-alpine
WORKDIR /home/node/app
ADD package*.json ./
RUN npm i
USER node
This configuration means:
- Get node image.
- Set up work directory
/home/node/app
inside container. - Copy
package.json
to container. - Install all dependencies.
- Change to node user
And we will use a docker-compose
like that:
version: "3.7"
services:
web-server:
build:
context: .
dockerfile: Dockerfile.dev
container_name: example-web-server
volumes:
- ./src:/home/node/app/src
- ./nodemon.json:/home/node/app/nodemon.json
expose:
- "8080"
ports:
- "8080:8080"
- "9229:9229"
command: npm start
We set up with:
- Version 3.7.
-
A service named
web-server
with next configuration:- Set current directory as
context
. - Get the file
Dockerfile.dev
. - Name container as
example-web-server
. - Set volumes
src
andnodemon.json
. - Expose port 8080 to other services.
- Bind host ports with container ports
8080
and9229
. - Run npm script
start
.
- Set current directory as
NOTE: You also can set up more services
At this point you can exec next command to up the services:
docker-compose up -d
Get node_modules from container (optional)
You can copy the node_modules
directory to the Typescript interpreter works correctly if you have not installed the packages locally.
docker cp example-web-server:/app/node_modules .
Set up debugger in VSCode (optional)
You can set up a remote debugger in VSCode appending the next configuration into the file launch.json
.
{
"type": "node",
"request": "attach",
"name": "Docker node",
"address": "localhost",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app",
"protocol": "inspector"
}
Now you can start the remote debugger.
Preparing production environment with docker
Finally we build the production docker image with next settings:
FROM node:10.15.3-alpine
WORKDIR /home/node/app
ADD . .
ENV NODE_ENV=production
RUN npm ci
USER node
EXPOSE 8080
CMD [ "node", "build/index.js" ]
As you can see Dockerfile
looks to similar to Dockerfile.dev
just have a few differences:
- Set up
NODE_ENV
toproduction
. - We replaced
npm i
fornpm ci
becausenpm ci
was maked to be used in automated environments such as test platforms, continuous integration, and deployment (you can see more here). - We don't use
npm
to start the server because "first off this reduces the number of processes running inside of your container. Secondly it causes exit signals such as SIGTERM and SIGINT to be received by the Node.js process instead of npm swallowing them" (best practices).
Build code code to production
In this part, we will delegate this job to the CI/CD
tool such as Travis, Jenkins, Circle CI, etc.
For this example we will use Travis
language: node_js
node_js:
- 10
branches:
only:
- master
- develop
- "/^travis-.*$/"
services:
- docker
before_install:
# Install dependencies
- gem update --system
- npm install
- npm run build
# Other jobs to prepare your environment
# like AWS settings, GCP settings, etc
- docker info
- echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_LOGIN --password-stdin
install:
- docker build -t your_docker_id/example-image .
script: "true"
after_success:
# Scripts to tag and push images to docker hub
# Scripts to deploy to cloud service like GCP, AWS, Azure, etc
As you can see in this part we transpile Typescript to Javascript with command npm run build
that runs tsc
which get settings from tsconfig.json
.
Summary
Docker can allow you develop in any where. You just need to set up Dockerfile.dev
and docker-compose.yml
to develop environment. Then you even can set up Dockerfile
and a CI/CD
tool to deploy production environment to some cloud service.
Maybe looks hard to set up but its just at begins and later to every developer is enough with runs docker-compose up
Finally I invite you to try Docker in your Typescript projects because have more pros than cons. If you have some questions just message me.
Have fun 🙂
Github repository https://github.com/HeberGB/example-typescript-docker