8

I hope you can help me with my problem! Here is the info.

Situation

I currently have two working containers that I need to run on the same port 80. There is the website, which currently is accesible by simply going to the host url of the server, and the restful api. However, it has to work by going all throug the port 80 and the login makes requests to the restful api which would have to listen on port 80 too to handle the requests. Therefore, from what I see I'd need a reverse proxy such as nginx to map the interl/external ports appropriately.

Problem

I really don't understand the tutorials out there when it comes to dockerizing an nginx reverse proxy along with two other containers... Currently, the restful api uses a simple Dockerfile and the application uses a docker-compose along with a mysql database. I am very unsure as to how I should be doing this. Should I have all of these inside one folder with the nginx reverse proxy and then the docker-compose handles all the subfolers which each have dockerfiles/docker-compose? Most tutorials I see talk about having two different websites and such, but not many seem to talk about a restful api along with a website for it. From what I understand, I'd most definitely be using this docker hub image.

Docker images current structure

- RestApi
 - Dockerfile
  - main.go

- Website
 - Dockerfile
 - Docker-compose
 - Ruby app

Should I create a parent folder along a reverse-proxy folder and put all these 3 in the parent folder? Something like :

- Parentfolder
 - Reverse-proxy
 - RestApi
 - Website

Then there's websites that talk about modifying the sites-enabled folder, some don't, some talk about vritual-hosts, others about launching the docker with the network tag... Where would I put my nginx.conf? I'd think in the reverse-proxy folder and mount it, but I'm unsure. Honestly, I'm a bit lost! What follows are my current dockerfile/docker-composes.

RestApi Dockerfile

FROM golang:1.14.4-alpine3.12

WORKDIR /go/src/go-restapi/

COPY ./testpackage testpackage/
COPY ./RestAPI .

RUN apk update
RUN apk add git

RUN go get -u github.com/dgrijalva/jwt-go
RUN go get -u github.com/go-sql-driver/mysql
RUN go get -u github.com/gorilla/context
RUN go get -u github.com/gorilla/mux

RUN go build -o main .

EXPOSE 12345

CMD ["./main"]

Website Dockerfile

FROM ruby:2.7.1

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y bash nodejs tzdata netcat libpq-dev nano tzdata apt-transport-https yarn

RUN gem install bundler

RUN mkdir /myapp
WORKDIR /myapp

COPY package.json yarn.lock Gemfile* ./
RUN yarn install --check-files

RUN bundle install

COPY . .

# EXPOSE 3000

# Running the startup script before starting the server
ENTRYPOINT ["sh", "./config/docker/startup.sh"]

CMD ["rails", "server", "-b", "-p 3000" "0.0.0.0"]

Website Docker-compose

version: '3'
services:
  db:
    image: mysql:latest
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    # volumes:
      # - ./tmp/db:/var/lib/postgresql/data
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_dev
      MYSQL_USERNAME: root
      MYSQL_PASSWORD: root

  web:
    build: .
    # command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # volumes:
      # - .:/myapp
    ports:
      - "80:3000"
    depends_on:
      - db
    links: 
      - db
    environment: 
      DB_USER: root
      DB_NAME: test_dev
      DB_PASSWORD: root
      DB_HOST: db
      DB_PORT: 3306
      RAILS_ENV: development

Should I expect to just "docker-compose up" just one image which will handle the two other ones with the reverse proxy? If anyone could point me to what they'd think would be a good solution to my problem, I'd really appreciate it! Any tutorial seen as helpful would be greatly appreciated too! I've watched most on google and they all seem to be skipping some steps, but I'm very new to this and it makes it kinda hard...

Thank you very much!

NEW docker-compose.yml

version: '3.5'
services:
  frontend:
    image: 'test/webtest:first-test'
    depends_on:
      - db
    environment:
      DB_USER: root
      DB_NAME: test_dev
      DB_PASSWORD: root
      DB_HOST: db
      DB_PORT: 3306
      RAILS_ENV: development
    ports:
      - "3000:3000"
    networks:
      my-network-name:
        aliases:
          - frontend-name

  backend:
    depends_on:
      - db
    image: 'test/apitest:first-test'
    ports:
      - "12345:12345"
    networks:
      my-network-name:
        aliases:
          - backend-name      

  nginx-proxy:
    depends_on:
      - frontend
      - backend
    image: nginx:alpine
    volumes: 
      - $PWD/default.conf:/etc/nginx/conf.d/default.conf
    networks:
      my-network-name:
        aliases:
          - proxy-name
    ports:
      - 80:80
      - 443:443

  db:
    image: mysql:latest
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_dev
      MYSQL_USERNAME: root
      MYSQL_PASSWORD: root
    ports:
      - '3306:3306'
    networks:
      my-network-name:
        aliases:
          - mysql-name
        
networks:
  my-network-name:

1 Answer 1

22

I wrote a tutorial specifically about reverse proxies with nginx and docker.

Create An Nginx Reverse Proxy With Docker

You'd basically have 3 containers and two without exposed ports that would be communicated through a docker network and each attached to the network.

Bash Method:

docker create my-network;
# docker run -it -p 80:80 --network=my-network ...

or

Docker Compose Method:

File: docker-compose.yml

version: '3'
services:
   backend:
      networks:
         - my-network
   ...
   frontend:
      networks:
         - my-network
   proxy:
      networks:
         - my-network
networks:
   my-network:
  • A - Nginx Container Proxy - MAPPED 80/80
  • B - REST API - Internally Serving 80 - given the name backend
  • C - Website - Internally Serving 80 - given the name frontend

In container A you would just have an nginx conf file that points to the different services via specific routes:

File: /etc/nginx/conf.d/default.conf

server {
    listen       80;
    server_name  localhost;
    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        proxy_pass http://frontend;
    }
    location /api {
        proxy_pass http://backend:5000/;
    }
    //...
}

This makes it so that when you visit:

Let me know if you have questions, I've built this a few times, and even added SSL to the proxy container.

This is great if you're going to test one service for local development, but for production (depending on your hosting provider) it would be a different story and they may manage it themselves with their own proxy and load balancer.


===================== UPDATE 1: =====================

This is to simulate both backend, frontend, a proxy and a mysql container in docker compose.

There are four files you'll need in the main project directory to get this to work.

Files:

- backend.html
- frontend.html
- default.conf
- docker-compose.yml

File: ./backend.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Backend API</title>
</head>
<body>
    <h1>Backend API</h1>
</body>
</html>

File: ./frontend.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Frontend / Website</title>
</head>
<body>
    <h1>Frontend / Website</h1>
</body>
</html>

To configure the proxy nginx to point the right containers on the network.

File: ./default.conf

# This is a default site configuration which will simply return 404, preventing
# chance access to any other virtualhost.

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # Frontend
    location / {
        proxy_pass http://frontend-name; # same name as network alias
    }

    # Backend
    location /api {
        proxy_pass http://backend-name/;  # <--- note this has an extra /
    }

    # You may need this to prevent return 404 recursion.
    location = /404.html {
        internal;
    }
}

File: ./docker-compose.yml

version: '3.5'
services:
    frontend:
        image: nginx:alpine
        volumes:
            - $PWD/frontend.html:/usr/share/nginx/html/index.html
        networks:
            my-network-name:
                aliases:
                    - frontend-name
    backend:
        depends_on:
            - mysql-database
        image: nginx:alpine
        volumes:
            - $PWD/backend.html:/usr/share/nginx/html/index.html
        networks:
            my-network-name:
                aliases:
                    - backend-name
    nginx-proxy:
        depends_on:
            - frontend
            - backend
        image: nginx:alpine
        volumes: 
            - $PWD/default.conf:/etc/nginx/conf.d/default.conf
        networks:
            my-network-name:
                aliases:
                    - proxy-name
        ports:
            - 1234:80
    mysql-database:
        image: mysql
        command: --default-authentication-plugin=mysql_native_password
        restart: always
        environment:
            MYSQL_DATABASE: 'root'
            MYSQL_ROOT_PASSWORD: 'secret'
        ports:
            - '3306:3306'
        networks:
            my-network-name:
                aliases:
                    - mysql-name
networks:
    my-network-name:

Create those files and then run:

docker-compose -d up;

Then visit:

  • Frontend - http://localhost:1234
  • Backend - http://localhost:1234/api

You'll see both routes now communicate with their respective services. You can also see that the fronend and backend don't have exposed ports. That is because nginx in them default port 80 and we gave them aliases within our network my-network-name) to refer to them.

Additionally I added a mysql container that does have exposed ports, but you could not expose them and just have the backend communicate to the host: mysql-name on port 3306.

If you to walkthrough the process a bit more to understand how things work, before jumping into docker-compose, I would really recommend checking out my tutorial in the link above.

Hope this helps.


===================== UPDATE 2: =====================

Here's a diagram:

enter image description here

Sign up to request clarification or add additional context in comments.

10 Comments

I'm not sure i understand, I don't want to "trigger" the api by going to a specific route, it's more like it's always running in the background. Say when the user tries to log in on page x, it makes the call to the api through let's say port 9876. So if I understand right, I'd have one docker-compose with the website, api, proxy and mysql? How would I go for the "ports" or "expose" though? For example, currently I can access my ruby app when I go to localhost because I mapped "ports" 80:3000 in the website service in the docker-compose. However, how would I do that with nginx? Thanks a lot!
The implementation would make it so that all your services are constantly up and running, it's not like serverless. The ports for the other services are completely up to you but a way I would structure it would be that the proxy would be the dominate one with the exposed port, it doesn't have to be port 80, it could technically be whatever you want, and the others don't need to be exposed because they will be communicated with through the docker network. I realize that I'm paraphrasing a longer explanation from my Medium article mentioned above, but I'll update the original answer.
@JeunePadawan check out the UPDATE section with the full instructions. Run it, that way you can see how it works.
Yeah, I will! I'm just not sure why the backend is a html file? I'd suppose that's just for the example! Thank you!
Yeah it's just so that you see the difference between the backend and frontend to show they are different servers. The files are mounted by via volumes:. If you http server is running a different port, in the default.conf you would modify it as proxy_pass http://backend:3000/.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.