Expected Reading Time: 8 minutes

In the era of microservices, Docker is considered the non plus ultra. Even front-end applications like Angular or back-end applications like Spring Boot (Java) can be easily used in Docker containers. When presenting an application to a larger group, this can come in handy.

When using your own server, the applications can be easily deployed and made available to everyone. This way, everyone can already familiarize themselves with the application during a presentation.

What is needed?

For the following example some programs and services are needed:

Backend
  • Gradle
  • Java 8 (JDK)
  • MySQL 8
Frontend
  • Node.js (version 10.13 or newer)
Microservice (Docker)
  • Docker

Frontend

You have to make sure in Angular that you can still set the connections between the Docker containers for deployment. The Docker containers communicate with each other on an internal network and thus have different IPs.

This is made possible in Docker by environment variables, but you can't use them as easily as, say, Spring Boot by using a @Value annotation can set.

Creating the Environment Variables for Use in Angular

So to make the whole thing possible a new script must be created in the folder /assets to be created and included. You name the file env.js with the following content:

(function (window) {
  window["env"] = window["env"] || {};

  window["env"]["apiUrl"] = "//localhost:8080";
  window["env"]["debug"] = true;
})(this);

The value specified here for apiUrl represents the default value if the variable is not set directly by Docker. Here I would like to point out again that all the code in the below in the Conclusion linked GitHub repositories.

Now, in order for the newly created JavaScript to be executed when Angular is launched, you must add it to the index.html add

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>DockerSpringAngular</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <script src="assets/env.js"></script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

Now you need to modify the actual environment files of Angular to enable setting by Docker with them. To do this, in the folder /environments the environment.ts be supplemented as follows. The same should also apply to the environment.prod.ts be done for productive operation. However, you must of course ensure that production: true is.

export const environment = {
  production: false,
  apiUrl: window["env"]["apiUrl"] || "default",
  debug: window["env"]["debug"] || false,
};

Due to this change, the values now come from our previously created script env.js. Currently, however, there is still no possibility these apiUrl but also dynamically for deployment.

Environment variables template for external definition

Docker needs a way to access a predefined variable name. To enable this, you also need to create a new variable name in the folder /assets another file named env.template.js create. From the basic principle the file looks like the env.js only with the difference that we now specify the variable name (here API_URL).

(function(window) {
  window.env = window.env || {};

  window["env"]["apiUrl"] = "${API_URL}";
  window["env"]["debug"] = "${DEBUG}"
})(this);

With the command envsubst you can then add the variables that are set in the template to the env.js insert. So for example with a specification of API_URL: http://localhost:8080. The end result will look like this:

envsubst  assets/env.js

Internal use in services

At user.service.ts environment must be imported as a variable and can then be used as a normal declaration. Thus it is possible to access the apiUrl and the stored path or address will be used.

import { environment } from "src/environments/environment";

const USER_API = environment.apiUrl + "/api/user";
const SAVE_USER_API = USER_API + "/create";

Creation of the Docker image

For this you need a Dockerfile. You can also find this in the GitHub repository in the folder docker.

#################
# Build the app #
#################
FROM node:14.5.0-alpine as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY .
RUN npm install -g @angular/cli
RUN ng build --configuration production --output-path=/dist

################
# Run in NGINX #
################
FROM nginx:alpine
COPY --from=build /dist /usr/share/nginx/html
COPY ./nginx-custom.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["/bin/sh", "-c", "envsubst  /usr/share/nginx/html/assets/env.js && exec nginx -g 'daemon off;'"]

First of all node is used as a basis. Then all dependencies of the project are installed and the whole is built directly. The configuration is set to productive mode. Generally, building in advance allows less load for the person who downloads the Docker image and wants to start it. Because nothing has to be built anymore. Above all, this ensures significantly smaller image sizes.

So I used libraries like Angular Material or FontAwesome, but still only achieved an image size of 10 MB (see https://hub.docker.com/r/saschabrockel/docker-spring-angular/tags). Otherwise the entire node_modules folders are packed into the image. Thus, sizes of 400-700 MB would be reached quickly.

NGINX configuration

To make the built Angular application now also callable you have to create nginx as a web server still configure accordingly. For this purpose the nginx-custom.conf (can be found in the frontend folder).

server {
  listen 80;
  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html =404;
  }
}

The requests to the URL's are always forwarded, unless the URL does not exist. Then the user is always redirected to the home page.

Since you usually want to run a web application via a direct URL without specifying a port, enter the port EXPOSE 80 on. Finally, the environment variables are overwritten as described above.

With this, all arrangements are now made to connect the Angular frontend to any backend for deployment.

Backend

In Spring Boot, the whole thing is a bit less complicated, since all the prerequisites for dynamically setting variables already exist out of the box.

REST requests take place via the controller and therefore it must also contain our variable. The whole thing is enabled in the UserController by the @Value Annotation.

  @Value("${angular.service.base-path}")
  private String angularServiceBasePath;

This serves only as a small digression, since this is not used in this sample project. However, it has the same function as setting in Angular. Thus, another Spring Boot Server could possibly be addressed.

So that the variable can now also be set, the value specified in the @Value annotation now also appears in the properties file. application.properties can be declared with a default value.

angular.service.base-path=http://localhost:4200

All itself in application.properties can be overwritten in Docker by specifying the corresponding environment variable. Thus also the later in section Docker necessary declaration of the database connection.

spring.datasource.url=jdbc:mysql://localhost:3306/dockerSpringAngular?createDatabaseIfNotExist=true&serverTimezone=UTC

CORS configuration

The last part to consider is the tiresome topic of CORS. Basically, CORS is important for security, but often not so trivial to set up. It determines which web applications from another domain should be able to access the server.

According to my current knowledge, it is unfortunately not possible to set the CORS configuration for deployment as well. This has to be done before building the image. We can find the configuration in the WebSecurityConfig.

  @Bean
  public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.setAllowedOrigins(
        Arrays.asList("http://localhost:4200", "http://localhost", "http://your-domain.com"));
    config.setAllowedMethods(Arrays.asList("POST", "OPTIONS", "GET", "DELETE", "PUT"));
    config.setAllowedHeaders(
        Arrays.asList("X-Requested-With", "Origin", "Content-Type", "Accept", "Authorization"));
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
  }

Important for our project is the point config.setAllowedOrigins(). Hereby we define which domains are now allowed to send a request to the server. In our case actually only localhost:4200 for the development, then localhost for use in Docker on Windows. As an example then also the specification http://your-domain.com from which inquiries could then be sent.

Of course, CORS settings can still be made at controller level with the @CrossOrigin annotation can be taken. It is important that there is a CorsFilter is, since with a CorsConfigurationSource the settings do not work for this sample project.

Docker

Last but not least, we get to linking and running the whole thing in Docker. I will skip the Dockerfile of the backend in the explanation, because there are no special features there.

Docker Compose

  angular:
    container_name: angular
    ports:
      - 80:80
    image: saschabrockel/docker-spring-angular:frontend
    restart: unless-stopped
    environment:
      API_URL: http://localhost:8080
      TZ: Europe/Berlin
    networks:
      - docker-spring-angular

The Angular section is very trivial. The container runs as long as it is not stopped. The port is as already described in the Frontend section mentions 80 in order to not need to specify the port in the browser. Now the environment variable comes into play. API_URL is defined and points to the address of the Spring Boot container. The network is used for the exchange between the containers.

  spring-boot:
    container_name: spring-boot
    ports:
      - 8080:8080
    image: saschabrockel/docker-spring-angular:backend
    environment:
      spring.datasource.url: jdbc:mysql://db:3306/dockerSpringAngular
      spring.datasource.username: brockel
      spring.datasource.password: develop
      angular.service.base-path: http://localhost
      TZ: Europe/Berlin
    depends_on:
      - db
    restart: unless-stopped
    networks:
      - docker-spring-angular

Spring Boot looks very similar. Here we then set in the environment mainly the variables that set the database connection. It is important that the variables match those used to create the database.

  db:
    container_name: db
    image: mysql:8.0.21
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: dockerSpringAngular
      MYSQL_USER: brockel
      MYSQL_PASSWORD: develop
    ports:
      - 3306:3306
    restart: unless-stopped
    cap_add:
      - SYS_NICE
    networks:
      - docker-spring-angular

How to vote spring.datasource.username and MYSQL_USER so that the connection can be established in the first place. It becomes special again with the exact declaration of the database. MYSQL_DATABASE: dockerSpringAngular specifies the name of the database. But to use it then we have to specify the corresponding spring.datasource.url adjust. Here is a comparison of the line in application.properties and in the docker-compose.yml.

spring.datasource.url=jdbc:mysql://localhost:3306/dockerSpringAngular?createDatabaseIfNotExist=true&serverTimezone=UTC
spring.datasource.url: jdbc:mysql://db:3306/dockerSpringAngular

Instead of localhost we now enter the name of the database container (db) to establish the connection. In addition, the other Docker-level attributes are dropped.

There is to note that with the current docker-compose.yml, no data is stored as no volumes have been specified.

Conclusion

Now you have learned how to deploy Angular to Docker using Spring Boot. Here how to define each API endpoint when defining Docker services and what customizations need to be made for this.

Once the prerequisites are in place, it is easy to make adjustments to the deployment such as changing the server. Feel free to try it out for yourself.

User overview of the sample application
Example application

The whole thing should then look like this in the browser. In the individual project folders of the GitHub repository you will find README's with documentation and DeepL access to individual files.
Source for setting the Angular variables: https://pumpingco.de/blog/environment-variables-angular-docker/

You can find the complete source code on GitHub.
The Docker images are available in the DockerHub.


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

en_US