Dockerize Your Angular App with a Java Backend
In today's fast-paced digital landscape, developers face numerous challenges when building and deploying web applications. With the rise of microservices architecture and distributed systems, it's becoming increasingly important to ensure that your application is scalable, secure, and efficient.
One effective way to achieve these goals is by containerizing your application with Docker. In this blog post, we'll explore how to dockerize an Angular app with a Java backend using Docker containers.
What is Docker?
Docker is a lightweight and portable way to package, ship, and run applications in containers. Containers are similar to virtual machines but more lightweight and efficient. They provide a consistent and reliable environment for your application to run in, regardless of the underlying infrastructure.
Benefits of Containerizing Applications with Docker
Containerizing your application with Docker offers several benefits:
- Improved Portability: Containers can run on any platform that supports Docker, including Linux, Windows, and macOS.
- Consistent Environment: Containers provide a consistent environment for your application to run in, which ensures that it behaves the same way on different systems.
- Efficient Resource Utilization: Containers share the host machine's kernel, which means they require less resources (such as memory and CPU) compared to virtual machines.
- Easy Collaboration: Docker containers make it easy to collaborate with others by providing a consistent environment for development, testing, and production.
Prerequisites
- List the required tools and software:
- Docker Desktop (or a similar Docker environment)
- Docker Hub account (optional)
Dockerizing Angular App
We will start with creating file named Dockerfile in the root of angular app. Then add the following content in it
# Stage 1: Build the Angular application
FROM node:22-alpine as build_stage
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json to the working directory
COPY package.json package-lock.json* ./
# Clear npm cache and install dependencies with clean slate
RUN npm cache clean --force && \
npm install --force
# Copy the rest of the application code
COPY . .
# Run build
RUN npm run build:production
# Stage 2: Serve the Angular application using nginx
FROM nginx:alpine
# Copy the built Angular application from the build_stage to nginx's web directory
COPY --from=build_stage /app/dist/sample-app-frontend/browser /usr/share/nginx/html
# Expose port 80
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]Dockerfile
Here's an explanation of each line in the provided Dockerfile:
FROM node:22-alpine as build_stage
- This tells Docker to use a pre-existing image (in this case,
node:22-alpine) as a base for our new image and assign a temporary tag (build_stage) to the resulting intermediate image.
WORKDIR /app
- This sets the working directory inside the container to
/app. All subsequentCOPYandRUNcommands will operate from this directory.
COPY package.json package-lock.json* ./
- This command copies files or directories from the current build context (the directory containing the Dockerfile) into the container.
RUN npm cache clean --force && npm install --force
- This command executes a shell command inside the container. Clears the npm cache to ensure dependencies are reinstalled from scratch. Then install the dependecies.
COPY . .
- This line copies the rest of the application code (everything except
package.jsonandpackage-lock.json) into the container.
RUN npm run build:production
- This command builds the Angular application using the npm script build:production.
FROM nginx:alpine
- This starts a new stage for serving the built Angular application with Nginx. This is done to keep the final image light weight, we don't need full source code and compiler to run the application
COPY --from=build_stage /app/dist/sample-app-frontend/browser usr/share/nginx/html
- The command copies the built Angular application from the previous
build_stagestage into the Nginx container. - The name
sample-app-frontendin/app/dist/sample-app-frontend/browseris the name of app defined in angular.json
EXPOSE 80
- This line exposes port 80, which is used by Nginx to serve HTTP requests.
CMD ["nginx", "-g", "daemon off;"]
- The final command sets the default command for the container. It starts Nginx with the
daemon offflag, ensuring it runs in foreground mode.
Dockerizing Java App
We will create a file named Dockerfile in the root of java app. And add the following content in it
FROM maven:3.9.9-sapmachine-21 as build_stage
#Change working directory to /app
WORKDIR /app
# Copy files to /app directory
COPY pom.xml ./
COPY . .
#Execute maven build
RUN mvn package
#Copy build from /app to /
RUN cp target/sample-app-backend*.jar /sample-app-backend.jar
#Change working directory to /
WORKDIR /
FROM eclipse-temurin:21-jre-alpine
# COPY compiled jar file to new image
COPY --from=build_stage sample-app-backend.jar .
# Deploy Build
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "sample-app-backend.jar"]
Dockerfile
Here's an explanation of each line in the provided Dockerfile for the Java app:
FROM maven:3.9.9-sapmachine-21 as build_stage
- This tells Docker to use a pre-existing image (in this case,
maven:3.9.9-sapmachine-21) as a base for our new image and assigns a temporary tag (build_stage) to the resulting intermediate image.
WORKDIR /app
- This sets the working directory inside the container to
/app. All subsequentCOPYandRUNcommands will operate from this directory.
COPY pom.xml ./
- This command copies
pom.xmlfile from the current build context (the directory containing the Dockerfile) into the container.
COPY . .
- This command copies
srcdirectory from the current build context (the directory containing the Dockerfile) into the container.
RUN mvn package
- This line runs Maven's
packagegoal to build and compile the Java application. Maven's output (including the compiled JAR file) is stored in the/target/directory.
RUN cp target/sample-app-backend*.jar /sample-app-backend.jar
- This line copies all files with names matching
sample-app-backend*.jarfrom the/target/directory to a new location (/sample-app-backend.jar). - The name
sample-app-backend.jaris the name of app defined in pom.xml
FROM eclipse-temurin:21-jre-alpine
- This line starts a new stage for serving the built jar file with jre. This is done to keep the final image light weight, we don't need full source code and jdk to run the application
COPY --from=build_stage sample-app-backend.jar .
- This command copies the built jar file from the previous
build_stagestage into the JRE container.
EXPOSE 8080
- This line exposes port 8080, which is used by Java applications for HTTP requests.
ENTRYPOINT ["java", "-jar", "sample-app-backend.jar"]
- The final command sets the default command for the container. It start the jar file and bring up the backend
With this we are finished with updating our angular and java apps. Now we can move to utilize these newly created DockerFile and build the images
Building the images
We will start will creating a new folder named deployment where backend and frontend folder are present. Inside this folder we will create a file to build the images. We will name this file as docker-compose.build.yml. The content of the file will be :
services:
sample-app-frontend:
image: "sample-app-frontend:latest"
build:
context: ../frontend
dockerfile: Dockerfile
sample-app-backend:
image: "sample-app-backend:latest"
build:
context: ../backend
dockerfile: Dockerfiledocker-compose.build.yml
The file will be used build both frontend and backend docker images using it respective DockerFile . Once done, we can run the following command in terminal/command prompt.
docker compose -f .\deployment\docker-compose.build.yml buildcommand
Result will be something like this

You can also check the build image from docker desktop

You can also use the following command
docker imagescommand
Result will be something like this

Running the images
We are now in the home stretch of the process. We will create three more file in the deployment directory
networks:
webnet-sample-app:
driver: bridge
services:
sample-app-frontend:
image: "sample-app-frontend:latest"
restart: unless-stopped
ports:
- "8081:80"
networks:
- webnet-sample-app
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # Mount NGINX configuration file
sample-app-backend:
image: "sample-app-backend:latest"
restart: unless-stopped
env_file:
- .env.local
networks:
- webnet-sample-appdocker-compose.yml
The file is mostly similar to docker-compose.build.yml file, but this time instead of building the apps, this file will be used to run the built images. The networks section creates a network named webnet-sample-app using the default bridge driver, which allows containers to communicate with each other within this network. Both services are then configured to use this network for communication. Additionally, several key configuration options have been added:
ports:: Exposes port 8081 on the host machine and maps it to port 80 inside the container, allowing external access to the application.restart: unless-stopped: Configures Docker Compose to restart the containers if they exit due to a failure or signal, but not when stopped manually.volumes:: Mounts an NGINX configuration file located in./nginx.confas read-only (ro) within the container at/etc/nginx/nginx.conf, allowing custom configurations to be applied without modifying the image itself..env.localis an environment variables file that stores sensitive information, such as database credentials. This file is not committed to version control, which means it's not shared with others or stored in the repository.
events {}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Serve the index.html for all requests to support client-side routing
location / {
try_files $uri $uri/ /index.html;
}
location /api {
# 'sample-app-backend' is the docker image name of the backend
proxy_pass http://sample-app-backend:8080;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_ssl_server_name on;
proxy_ssl_verify off;
}
}
}
nginx.conf
This is an Nginx configuration file that sets up a web server to serve the Angular application. /api { ... }: This block is used for proxying requests to the backend server (sample-app-backend:8080). It sets up proxy headers to forward information like the original request URL and IP address.
#Database
MARIADB_HOST_NAME=
MARIADB_PORT=
MARIADB_DB_NAME=
MARIADB_USER=
MARIADB_PASSWORD=.env.local
Deploying the Images
We can run the following command in terminal/command prompt.
docker compose -f .\deployment\docker-compose.yml upcommand
Result will be something like this

At this point you should be able to access the app on your browser using the following url
http://localhost:8081

Pushing the image to repo
With the app built and running successfully on your machine, it can be easily shared with rest of your team or even deployed on cloud. All you need is to push the image to a docker repository using this command
docker push sample-app-frontend:latest
docker push sample-app-backend:latestcommand
The commands will push the images to you dockerhub repo.
Pushing to custom repo
If you want to push the images to a custom location say example.repo.com, you can tag the image with it and push the custom repo
docker tag sample-app-frontend:latest example.repo.com/sample-app-frontend:latest
docker tag sample-app-backend:latest example.repo.com/sample-app-backend:latestcommand
Now when you will push the image, it will saved in custom repo
docker push example.repo.com/sample-app-frontend:latest
docker push example.repo.com/sample-app-backend:latestcommand
Deploying on other computer/server
To deploy the image somewhere else, you just need these
- Access to image repo
- docker-compose.yml, nginx.conf, .env.local files
Once you set the database credentials in .env.local file, you can just run the following command and the app will spin up.
docker compose -f docker-compose.yml upcommand
Conclusion
Through this blog post, we've demonstrated how to dockerize an Angular app with a Java backend using Docker containers, making it easier to deploy, manage, and scale your application. By following these steps and embracing containerization, you'll be well-equipped to tackle the challenges of building complex web applications in today's digital landscape.
GitHub Repository:
You can find all the code from this blog on GitHub: