Dockerizing a MEAN-stack application - Part 3
This is the final part of the series on how to dockerize a MEAN stack app.
The source code of the project can be downloaded from my GitHub page.
Dockerize the client
So far so good, but the application has a client side built in Angular. It would be great if we could search, save and delete movies through the user interface.
Communication between the front end and the back end inside containers has its nuances. The browser is not part of the containers and therefore we can’t just simply add the name of the container to the request URL in the client-side app. This, by itself, won’t work.
So what is the solution?
First, I wanted the app to only accept requests on port 80 as discussed in the server side part. Therefore all URLs in the Angular app should refer to
192.168.99.100 on Windows. More on that later.
Second, the Angular app needs to be served by a simple web server. I chose nginx, which will also act as a reverse proxy, and forwards any database-related query to the API.
Dockerfile for the client
I needed a dockerfile for the client as well. I created this simple one in a file called
client.Dockerfile in the root folder:
FROM nginx:alpine LABEL description "Angular client side" COPY nginx.conf /etc/nginx/nginx.conf WORKDIR /usr/share/nginx/html COPY dist/ .
The first interesting line is the first
COPY command. I created an
nginx.conf configuration file in the root project folder (see below), which is copied to the relevant folder in the Docker container. Again, I took the
/etc/nginx/nginx.conf path from the official nginx image description in Docker Hub.
Similarly, I define the working directory, and then copy the content of the build from the
dist folder (created with
ng build --prod) to the working directory.
The dockerfile above refers to a simple
nginx.conf file, which looks like this:
The important configurations are in the
types directive, I defined the mime types that are used in the application. I needed to do this so that the relevant files display correctly.
server directive contains the web and proxy server configurations. If the
localhost) endpoint is hit, the content from the
/usr/share/nginx/html will be served. This path fragment refers to the Docker container and was defined in the client side dockerfile as working directory (see above).
All route endpoints defined in
server.ts start with
/api. When these endpoints are hit, the server makes the relevant query (
DELETE) to the database. In these cases, I want to map the client side request to the server.
location block is a simple proxy pass, which is responsible for the redirection. The most important part of the configuration is the
As it can be seen, it’s not
https://localhost/api where the redirection occurs. This would work fine if we didn’t have Docker set up for the application.
In our case though, we need to refer to the name of the service (the Node/Express server) in Docker, and this is
node-api (see the
docker-compose.yml in Part 2). This is one of the most important things that can go wrong. If the names don’t match, the whole application won’t work.
Now that the nginx server is set up on the client side, I’ll need to create a Docker image and a container, so that they could work together.
I added a third service and a second network to my
docker-compose.yml file, which looks like this:
version: '3' services: db: image: mongo:latest container_name: mongo-movie-app-container volumes: - mongo-data:/data/db networks: - api-network node-api: build: context: . args: - mongodb_container - app_env image: node-express-movie-api container_name: node-movie-api-container env_file: .env networks: - movie-network - api-network depends_on: - db client: build: context: . dockerfile: client.Dockerfile image: client-side-movie-app container_name: client-movie-app-container ports: - "80:80" networks: - movie-network depends_on: - node-api volumes: mongo-data: networks: movie-network: api-network:
The new service is called
client, which refers to the client side served by nginx (it can be named differently).
build instruction is similar to the
context is the root folder (
.) and here I have to specify the
dockerfile, because the default filename
Dockerfile is already used for the Node server container.
I named both the image and the container to make it easier to identify them.
ports part is important. I want the app to be accessible through one port only, so I opened up port
80 both in the host (i.e. the computer, first
80) and inside the container (second
80, after the colon). With this, when I just type
192.168.99.100 on Windows) in the browser, the Angular client will be seen.
In the next step, I connect the client to a new network called
movie-network, which includes both the Node/Express API and the front end build, but not the database. The database is only connected to the API, but not to the client.
This means that a search for a movie in the search box of the client build will be submitted to the API (through
movie-network) and the API will forward this request to the database through
api-network. The only point of connection of the MongoDB database is the server, and every query to the database has to go through the server. The database is not directly accessible from the client.
The new network also needs to be declared as a network in the
The last instruction is the
depends_on, and the client side container is depended on the API (
node-api, the name of the API service).
Start the app
With this, the app is now fully dockerized and ready to start.
If I run the
docker-compose up command for the project folder, Docker downloads and builds the images (if it hasn’t already done so), and by navigating to
192.168.99.100 on Windows) in the browser, I can now search, save and delete movies.
This concludes Part 3 and the series on dockerizing a MEAN stack app.
The process itself was not particularly hard, but I encountered some minor issues that took me some time to figure out.
The key issue is to connect the containers in the appropriate way, so that they can talk to each other.
I hope that this series of posts are useful and can save some time for others trying to do something similar.
Thanks for reading and see you next time.