Say, you’ve an Angular app that talks to a backend service through a network. Both of them are running in separate containers, which can be on the same or different Docker stack(s). You want only the Angular container to be publicly accessible through a port.
Let’s run through some scenarios on how you can achieve this.
We’d need an Angular app and a backend service to begin with.
Create a backend service
Let’s create a very simple backend that will return a greeting message when a /hello
endpoint will be called. We’ll use Micronaut to generate this service. Install Micronaut CLI and execute the following command on the terminal.
mn create-app dev.mflash.guides.greeter.greeter-api --build maven
This will generate a basic Micronaut app with Maven support. Import this app in your favorite IDE.
Create a class for greeting message.
// greeter-api/src/main/java/dev/mflash/guides/greeter/Greeting.java
public class Greeting {
private String message;
// constructors, getters, setters, etc
}
Configure an endpoint, say /hello
, that returns a Greeting
object.
// greeter-api/src/main/java/dev/mflash/guides/greeter/GreetingController.java
@Controller("/hello")
public class GreetingController {
@Get
public Greeting greet(@QueryValue(value = "name", defaultValue = "World") String name) {
return new Greeting(String.format("Hello, %s!", name));
} }
To let the Angular app call this endpoint, we’ll have to enable CORS. To do this, edit application.yml
file and add the following configuration.
# greeter-api/src/main/resources/application.yml
micronaut:
application:
name: greeter-api
server:
port: 8084
cors:
enabled: true
configurations:
web:
allowedOrigins:
http://0.0.0.0:4200 -
This backend will run on an embedded server on the port 8084
and will accept all the requests coming from http://0.0.0.0:4200
.
Edit the Dockerfile
in the project root and overwrite it with the following configuration.
# greeter-api/Dockerfile
FROM maven:3.6.1-jdk-13-alpine as builder
WORKDIR /usr/home/greeter
COPY . .
RUN mvn package
FROM adoptopenjdk/openjdk13-openj9:jre-13.0.2_8_openj9-0.18.0-alpine
COPY --from=builder /usr/home/greeter/target/greeter-api-*.jar greeter-api.jar
EXPOSE 8084
CMD ["java", "-jar", "greeter-api.jar"]
To build a Docker image, execute the following command on the terminal.
docker build -t mflash/greeter-api .
Create an Angular app
For the sake of example, let’s generate a very minimal Angular app with no routing, specifications or external templates. Execute the following command on the terminal.
ng new greeter-ui --minimal --routing=false --style=css --skipTests --inlineStyle --inlineTemplate
Refer to
ng new
reference for more information on these options.
To call the REST endpoint, we’ll need HttpClientModule
. Add it in the root module of the Angular app.
// greeter-ui/src/app/app.module.ts
import { HttpClientModule } from '@angular/common/http';
NgModule({
@BrowserModule, HttpClientModule]
imports: [// declarations, providers, bootstrap, etc
})export class AppModule {}
Create a GreetingService
by executing the following command.
ng generate service Greeting
Add a method (getGreeting
) to call the backend service.
// greeter-ui/src/app/greeting.service.ts
Injectable({
@'root'
providedIn:
})export class GreetingService {
constructor(private client: HttpClient) {}
getGreeting() {
return this.client.get('/hello?name=Microflash');
} }
Edit AppComponent
to display the greeting message.
// greeter-ui/src/app/app.component.ts
Component({
@'app-root',
selector: `
template: <main>{{ message }}</main>
`,
styles: []
})export class AppComponent {
message: string = 'Hello, stranger!';
constructor(private greetingService: GreetingService) {}
ngOnInit(): void {
this.getMessage();
}
getMessage() {
this.greetingService.getGreeting()
subscribe(response => (this.message = response['message']));
.
} }
To serve the production build of this app, we can use Nginx. Create a default.conf
file in the root of the Angular app and add the following configuration.
# greeter-ui/default.conf
server {;
listen 4200
;
sendfile on
;
default_type application/octet-stream
;
gzip on;
gzip_http_version 1.1"MSIE [1-6]\.";
gzip_disable ;
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9
;
root /usr/share/nginx/html
location / {;
index index.html index.htmindex.html =404;
try_files $uri $uri/ /
}
location /hello {'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header
request_method = 'OPTIONS') {
if ($'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
add_header ;
return 204
}
request_method = "POST|GET") {
if ($'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
add_header
}
;
proxy_pass http://0.0.0.0:8084
} }
Don’t worry too much about this configuration; it instructs nginx
to
- listen for any requests at port
4200
- serve files from a directory
/usr/share/nginx/html
- serve
index.html
at/
- redirect the requests coming at
/hello
tohttp://0.0.0.0:8084/hello
The last point is important where nginx
will serve as a proxy for the backend service.
Create a Dockerfile
in the root of the Angular project and add the following.
# greeter-ui/Dockerfile
# Generate a build
FROM node:12-alpine as builder
WORKDIR /app
COPY . /app
RUN yarn
RUN yarn build
# Serve with Nginx
FROM nginx:1.17-alpine
RUN rm -rf /usr/share/nginx/html/*
COPY default.conf /etc/nginx/conf.d/
COPY --from=builder /app/dist/greeter-ui /usr/share/nginx/html
EXPOSE 4200
CMD ["nginx", "-g", "daemon off;"]
Generate the image by running the following command.
docker build -t mflash/greeter-ui .
Scenario 1: Containers on the same Docker stack
Consider the first scenario where the Angular app and the backend service are running on the same Docker stack. Create a compose file and add the following configuration.
# scenario-1-on-the-same-network/docker-compose.yml
version: '3'
services:
greeter-ui:
image: mflash/greeter-ui
networks:
greeternet
- ports:
4200:4200
-
greeter-api:
image: mflash/greeter-api
networks:
greeternet
-
networks:
greeternet:
driver: bridge
Note that only
greeter-ui
is exposed publicly.
Launch this stack by executing docker-compose up -d
and browse to http://localhost:4200. You’ll see the following message displayed on the browser window.

This is the default greeting message; we’re not getting the message from the backend service. A quick look on the Networks tab in Devtools reveals that we’re getting a 502 Bad Gateway
for the request at http://0.0.0.0:8084/hello?name=Microflash. That’s because Docker assigns some random host to a service; in this case, it is not 0.0.0.0
.
To resolve this issue, open default.conf
file and replace the host 0.0.0.0
in the proxy_pass
value with the service name you specified in docker-compose.yml
(in our case, it’s greeter-api
).
# greeter-ui/default.conf
server {# other configurations
location /hello {# other configurations
;
proxy_pass http://greeter-api:8084
} }
Rebuild the greeter-ui
image, launch the stack again and browse to http://localhost:4200. You’d see the expected greeting message.

Scenario 2: Containers on different Docker stacks
The service url http://greeter-api:8084 won’t work unless the containers share the same network. For the containers running on different Docker stacks, we can configure them to connect through the same network.
Create a compose file and add the following configuration for the backend service.
# scenario-2-on-different-networks/greeter-api/docker-compose.yml
version: '3'
services:
greeter-api:
image: mflash/greeter-api
networks:
greeternet
-
networks:
greeternet:
driver: bridge
In this file, we’re specifying the backend service and the network greeternet
associated with this stack.
Create another compose file and add the following configuration for the Angular app.
# scenario-2-on-different-networks/greeter-ui/docker-compose.yml
version: '3'
services:
greeter-ui:
image: mflash/greeter-ui
networks:
uinet
- ports:
4200:4200
-
networks:
uinet:
external:
name: greeter-api_greeternet
In this file, we’re declaring the Angular app exposed over the port 4200
and the network uinet
which is nothing but a reference for greeternet
. This will ensure that the services on both the stacks can talk to each other over the network greeternet
.
Launch both the stacks with docker-compose up -d
and browse to http://localhost:4200. You would see the expected message on the browser window.