If you are developing a web application then you will more than likely deploy the UI layer files in a simple web server to provide a fast edit/reload/test cycle. If you want to use the real backend services you’ll require a web application container such as Tomcat but you’ll hit the CORS problem. In this post, I will explain what CORS is and how you can configure NGINX as a reverse proxy in Docker to avoid it.

What is CORS?

CORS stands for Cross-origin resource sharing. More detailed information can be found on Wikipedia but in essence, it is a mechanism that prevents cross-site scripting (XSS) attacks.

For instance, if we think of a web application that was built for and hosted on the domain www.myamazingwebapp.com and it made XMLHttpRequest (XHR) calls to a REST service on the same domain name, then the origin of the XHR requests would be the same as that of the REST service.

If a hacker was somehow able to modify the behaviour of the application and change the XHR requests to point at www.myamazingpasswordstealer.com then they could intercept confidential data. CORS requests are disabled by default to prevent this such that in this case the origin of the XHR requests would not be the same and therefore the request would not be allowed.

It’s all just a bit messy and not something you should have to code unless you really are wanting to make cross-origin XHR requests.

As a developer, you may decide to deploy the UI part of an application in one web container, i.e. NGINX or Apache and your application backend in a web container such as Tomcat or WildFly on your local machine. Both containers will run on localhost but on different ports. Unfortunately, this is still across domain as it has to be the same port so this doesn’t help us during development.

You can Enable CORS but this requires changes to web container configuration to support CORS requests whereby you allow another domain to make a cross-origin request. You also need specific Javascript calls to make the XMLHttpRequest. It’s all just a bit messy and not something you should have to code unless you really are wanting to make cross-origin XHR requests.

How does a reverse proxy help?

A reverse proxy acts as a go-between for a client. The client requests various resources from a single source but the reverse proxy could be forwarding the request to other servers and returning the result. The client has no knowledge of this.

For a web application, this means that all XMLHttpRequest (XHR) requests can point at the same domain as the web application. e.g. if you deploy your UI on localhost:8080 then your XMLHttpRequest’s will also go to localhost:8080. If you have your backend deployed to a web container on localhost:9000 then the reverse proxy will forward requests onto the web container and return the result back to the client.

Sounds great. So how do I use NGINX as a reverse proxy in Docker?

To get started, let’s create a Docker Compose file to deploy the backend services to a Tomcat container and the UI to a NGINX container:

 1 2 3 4 5 6 7 8 910111213141516
  container_name: myapp-service
  image: yobibyte/tomcat:7.0.65-jdk-8
    - "9001:8080"
    - ./myapp-service/target/myapp-service.war:/usr/local/tomcat/webapps/myapp-service.war

  container_name: myapp-ui
  image: nginx
    - "8080:80"
    - ./myapp-ui/app:/usr/share/nginx/html
  command: /bin/bash -c "nginx -g 'daemon off;'"

This will create a Tomcat instance running on port 9001 and an NGINX instance running on port 8080. However, this won’t quite work yet. Any XHR requests that are relative will not work as the requests will go to NGINX rather than Tomcat. To fix this we need to configure NGINX to proxy calls to Tomcat.

This is pretty easy and requires a small configuration file:

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    server {
        location / {
            root    /usr/share/nginx/html;
            index   index.html;

        location /myapp-service {
            proxy_pass http://tomcat:8080;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile  off; # disable to avoid caching and volume mount issues

    keepalive_timeout  65;

The key here is in the http -> server part of the configuration where we configure two location blocks. The first location block simply directs all requests to the document root to serve the UI files. The second location block redirects any calls to /myapp-service to Tomcat.

You’ll notice that proxy_pass is a URL but how do we know what the IP address of the Tomcat container is? Well, this is easy thanks to Docker container links. We can simply add a link to the NGINX container to Tomcat such that we can refer to the Tomcat container as ‘tomcat’ in our NGINX configuration file.

We just need to update the NGINX container configuration in the Docker Compose file to override the NGINX configuration file with our custom one and add the link to Tomcat:


 1 2 3 4 5 6 7 8 91011
  container_name: myapp-ui
  image: nginx
    - tomcat
    - "8080:80"
    - ./angular-hello-world-ui/app:/usr/share/nginx/html
    - ./config/nginx.conf:/etc/nginx/nginx.conf
  command: /bin/bash -c "nginx -g 'daemon off;'"

Now any relative XHR requests will get forwarded to the Tomcat container without any CORS problems.

A working example of this in action can be found on GitHub where I have created a very simple AngularJS application and a Spring Boot REST backend. The Docker Compose file will deploy the application exactly as described above.

Please try it out and let me know what you think? What alternate ways have you used to get around this problem during development? Let me know in the comments below.