I started to learn devops recently. This article is basic tutorial on how to set up a node server, which receives requests via nginx.
This article was published on the 1st of September, 2019. The source code for the article is available here.
First we will set up two projects - the node app and the nginx app (which is just config). Let’s speed through the node app first, since nothing is very docker specific.
I have two directories, one called node
and one called nginx
.
Inside the node
I have a package.json
:
{
"dependencies": {
"express": "^4.17.1"
}
}
And index.js
:
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.json({ msg: 'hello from node' })
})
app.listen(3000, () => console.log('listening on 3000'))
Now it gets interesting. Add a Dockerfile
:
FROM node:12-buster-slim
RUN mkdir -p /app
WORKDIR /app
COPY . /app
RUN npm install express
CMD ["node", "index.js"]
We specify the image - in this case, we want a copy of debian buster running node.js v12. You can see what other node images are available here.
We create a new directory called app
, and set it are the current working directly. Then we copy all the files (which are package.json
, and index.js
) into app
, and run nopm install
. Lastly, we run node index.js
to start the app.
That’s it for the node project. If we added EXPOSE 3000
, we would be then able to access the server from outside the container. But we want to let nginx handle that.
We can build the project with docker build -t article_node:latest .
. After doing so, running docker image ls
shows:
REPOSITORY TAG IMAGE ID CREATED SIZE
article_node latest 3bd5d5eb24be 16 seconds ago 163MB
Among other images. Great!
Now it’s time to set up nginx. In the nginx
directly, add the following Dockerfile
:
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
We install nginx with a minimal linux distro, alpine
. Then we copy a file, nginx.conf
, which we are about to create, into /etc/nginx/nginx.conf
, which is where nginx looks for configuration files.
Create nginx.conf
with the following:
events {
}
http {
server {
listen 80;
location / {
proxy_pass http://node:3000/;
}
}
}
This is a simple nginx.conf
. There are two top level sections: events
, which I have not used before, and http
. In http
, we specify two options for the server
section. The first is which port to listen on: http uses port 80 by convention. The next is location
, which sets configuration based on a request URI. More info is here.
By doing location /
, we are matching ALL incoming requests. Since this simple nginx configuration is designed for the sole purpose of routing traffic to the node app, it’s fine. You can specify more granular regexps here for more complex use cases, such as an app with many different services.
We then set the proxy-pass attribute to http://node:3000/
- this maps to port 3000 on the node
domain. We will configure the naming for node
soon - at the moment nginx has no idea what to do with node
.
Build the nginx container by running docker build -t article_nginx:latest .
. Now docker image ls
yields:
REPOSITORY TAG IMAGE ID CREATED SIZE
article_node latest 3bd5d5eb24be 10 minutes ago 163MB
article_nginx latest 142d2932f2d3 36 minutes ago 21.2MB
You can see how much smaller alpine linux is than debian buster, which we used for the node image.
We will now use docker-compose
to unite the two apps. At the top level, next to the node
and nginx
directores, create a docker-compose.yml
. Inside it add the following:
version: '3'
services:
node:
image: article_node
nginx:
image: article_nginx
ports:
- 80:80
depends_on:
- node
By naming the service node
, docker maps this to a network of the same name (I believe - it works, and if you name the service anything else, it will not work). We also expose port 80 from the nginx container to the same port on our local machine. We also add depends_on
, which means when we run the nginx
container, node
will also start up.
You can run this in “detached” mode (in the background) by running docker-compose up -d nginx
. To make sure it’s working, do curl localhost:80
. It should return {"msg":"hello from node"}
. This means nginx is successfully proxying the request on port 80 to the node server, and then replying with the response! You can stop the containers by running docker-compose down
(must be run in the directory with the docker-compose,yml
of the container(s) you wish to stop).
This article is a very simple introduction to docker, docker-compose and nginx as a reverse proxy.