Building Angular apps has never been easier than when using the CLI. It's super easy to generate components, services, and modules. It's also really easy to do a production build of your code. But what wasn't so easy for me was taking the next step and actually deploying the app using Docker.
About a month ago, I needed to do just this for work. I spent some time learning a little more about Docker, and during that time I learned about multi-stage builds for Docker images. (Sidenote: they're amazing). So in the rest of this post I will explain the process of containerizing your application for deploying.
Let's start with a new project. After installing the latest version of the Angular CLI, run the following:
ng new my-docker-app
Move into the new directory, and run
ng serve -o
As you can see, your app is built and served locally. If you make changes, the browser will auto reload and reflect the changes you've made. Okay, so that's nothing new, but now we're on the same page. So let's move to the actual Docker work. We'll begin by creating a
Dockerfile where we can define the steps for building our image. We're going to use a multi-stage build like I talked about earlier, as well. Your Dockerfile will look like the following:
FROM johnpapa/angular-cli as angular-built WORKDIR /usr/src/app COPY package.json package.json RUN npm install --silent COPY . . RUN ng build --prod FROM nginx:alpine LABEL author="Preston Lamb" COPY --from=angular-built /usr/src/app/dist /usr/share/nginx/html EXPOSE 80 443 CMD [ "nginx", "-g", "daemon off;" ]
So to break down what happened here, let's look at the first block of instructions. The first section uses an image built by John Papa that has the Angular CLI baked in. It sets the working directory for the image, copies the package.json in, installs all the dependencies, and then copies the rest of the app files into the image. Then it builds the app with
RUN ng build --prod. But the important part for the multi-stage build is on the first line:
FROM johnpapa/angular-cli as angular-built.
as angular-built part will be used in the next step of our multi-stage build. So let's move to that next step. This time we're using an nginx image that will serve our application when the image we built is run inside a container. As you can see in the above steps, there's another
COPY command. But this time, instead of copying the files from the directory we're in into the image, we're copying it
--from=angular-built /usr/src/app/dist to
/usr/share/nginx/html. Obviously we're moving the code that we built in the first step into the nginx based image that will be our final image. To finish up, we expose a port and then give the command that will be run when the image is run inside a container.
Now that our Dockerfile is finished, we can build the image. You can do that with the following command:
docker build -t my-docker-app:latest .
That tells docker to build an image, give it a tag of my-docker-app:latest and to build it in the context of the current directory. When it's finished, you can see the image you build by entering
Now we're ready to run the docker container. You can do that with the following command:
docker run -d --port 8080:80 --name my-docker-app my-docker-app:latest
This command maps port 80 inside the container to 8080 on the host, names the container my-docker-app, and uses the my-docker-app image that we just built.
Now, if we were going to deploy this on a server, we'd really want to push that image we built to a registry from which we can pull the image on our server. Also, the nginx server inside that container needs a config that redirects all requests that come to the app's index.html file, since Angular is actually handling the routing.
Hopefully this has been helpful for you! I learned a lot going through the process, and hopefully this can be a good resource for you and others. Thanks!