pileup
pileup

Reputation: 3262

Adding second server block as reverse proxy breaks the server

I have 4 Docker containers:

  1. php-fpm for Laravel API
  2. node for Next.js frontend
  3. mysql
  4. nginx

Until now I was using nginx only to serve the Laravel API for testing - so I can directly call the API with Postman, and the Next.js frontend pages (the node container) were accessed directly by the host via localhost:3000. But now I want nginx to serve the frontend Next.js project, and then the node container will call the Laravel API, but I still want to leave the testing server for Postman, so I added a second server block to my default.conf file, and adjusted the docker-compose.yml file but it broke everything, now nothing is working.

This was my setup (that worked) until now:

The docker-compose.yml:

networks:
    laravel:
        driver: bridge

services:
    nginx:
        image: nginx:stable-alpine
        container_name: nginx
        ports:
            - "8088:80"
        volumes:
            - ./laravel-api:/var/www/html 
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
        
        depends_on:
            - php
            - mysql           
            - node             
        networks:
            - laravel
            
    mysql:
        image: mysql
        container_name: mysql
        restart: unless-stopped
        tty: true
        ports:
            - "4306:3306"
        volumes:
            - ./mysql:/var/lib/mysql
        environment:
            MYSQL_DATABASE: laravel          
            MYSQL_ROOT_PASSWORD: password
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql            
        networks:
            - laravel
                     
    
    php:
        build:
            context: .
            dockerfile: Dockerfile
        container_name: php
        volumes:
            - ./laravel-api:/var/www/html
        ports:
            - "9000:9000"
        networks:
            - laravel

    node:
        build:
            context: ./nextjs
            dockerfile: Dockerfile
        container_name: next
        
        volumes:
            - ./nextjs:/var/www/html
        ports:
            - "3000:3000"
            - "49153:49153"

        networks:
            - laravel
       

and nginx default.conf file:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/public;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ \.php$ {        
        try_files $uri = 404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    
}

So what is happening is that I access the node container directly on port 3000, and any call to the Laravel API is handled by nginx - redirecting requests from the host on port 8088 to the container of nginx on port 80.

What I tried to do was to add server block for reverse proxy from localhost:3000 to the nginx container like I do with the Laravel API calls:

This is the updated default.conf, added second server block:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html/public;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ \.php$ {        
        try_files $uri = 404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    
}

  server {
    listen       80;
    server_name  nextjs;
    access_log   logs/nextjs.access.log  main;


    
    location /nextjs {
      proxy_pass      node:3000;
    }
  }

And adjusted the docker-compose.yml file:

networks:
    laravel:
        driver: bridge

services:
    nginx:
        image: nginx:stable-alpine
        container_name: nginx
        ports:
            - "8088:80"
            - "3000:80"
        volumes:
            - ./nextjs:/var/www/html/nextjs
            - ./laravel-api:/var/www/html 
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
        
        depends_on:
            - php
            - mysql           
            - node             
        networks:
            - laravel
            
    mysql:
        image: mysql
        container_name: mysql
        restart: unless-stopped
        tty: true
        ports:
            - "4306:3306"
        volumes:
            - ./mysql:/var/lib/mysql
        environment:
            MYSQL_DATABASE: laravel          
            MYSQL_ROOT_PASSWORD: password
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql            
        networks:
            - laravel
                     
    
    php:
        build:
            context: .
            dockerfile: Dockerfile
        container_name: php
        volumes:
            - ./laravel-api:/var/www/html
        ports:
            - "9000:9000"
        networks:
            - laravel

    node:
        build:
            context: ./nextjs
            dockerfile: Dockerfile
        container_name: next
        
        volumes:
            - ./nextjs:/var/www/html
        ports:
            
            - "49153:49153"

        networks:
            - laravel

       

I removed the port 3000 from the node container, and added to the nginx and mapped to the port 80 of the nginx container, so any requests to port 3000 from my host will be handled by port 80 of nginx - just like Laravel, so now both Laravel and Nextjs are redirected to port 80 on the nginx container (Again, I leave the Laravel server so I can test API directly from the host as well - and not only from the Next.js website)

Also, I created a link for the nextjs project on the nginx container: - ./nextjs:/var/www/html/nextjs

But now nothing works when I go to either localhost:3000 or localhost:8088 from my host

Upvotes: 0

Views: 510

Answers (1)

Anthony Aslangul
Anthony Aslangul

Reputation: 3847

You'll need to change multiple things in order to use Nginx as a reverse proxy for your NextJS app.

First, in your docker-compose.yml, you must update the Nginx service:

services:
    nginx:
        image: nginx:stable-alpine
        container_name: nginx
        ports:
            - "3000:80"

As you can see, I removed the "8088:80" mapping. Why? Because, remember, this is used to map one port of the host to the service (it's always <host>:<container>). It means "if you open your browser and type http://localhost:8088, forward this request to the Nginx container, port 80".

Since you mapped two ports of your host to the port 80 in the Nginx container, doing http://localhost:8088 and http://localhost:3000 was exactly the same.

To fix this, we remove the unused port 8088. Let's keep only the port 3000.

Next, we'll update your first server block and make a change in the port it listens. Instead of the port 80, we'll now listen to the port 8088 (any available port is ok).

server {
    listen 8088;
    index index.php index.html;
    ...
}

Because you don't have any server_name configured (it would involve extra steps), we have to use different ports for each server blocks. By convention, since the frontend app will be accessed first before the API, we'll keep the port 80 on the Nextjs server block... so we had to choose another one for the API (and we chose 8088).

At this point, every requests made to http://localhost:3000 will land in your Nginx container on the port 80. Your second server block listens to the port 80, so let's dive into it:

server {
  listen       80;

    
  location / {
    proxy_pass http://node:3000;
  }
}

I removed the /nextjs, we want all requests from http://localhost:3000 be reverse proxied, not only those starting with http://localhost:3000/nextjs.

We also add http:// before node:3000 because it'll be a HTTP.

I also removed the server_name and the access log (doesn't strictly needed and might not exist, it can be added later when you have a working app).

The last change you'll have to make will be to tell your node app to send API requests to http://nginx:8088 (e.g. http://nginx:8088/api/products...).


To summarize what should happen:

  • You open your browser, you type http://localhost:3000/my-pretty-url
  • It lands in the Nginx container on the port 80 (because of the mapping "3000":"80")
  • Nginx sees it has a server block listening on that port and it looks at what it should do: proxy_pass http://node:3000;. Note that the port 3000 used here could have been totally different from the port used by your node app. If you start your node app with the port 6000, just replace http://node:3000 with http://node:6000
  • The request lands in your node app. If you have any API request on that url (let's say to /api/products), they should be done to http://nginx:8088/api/products
  • Back to the Nginx container, where this time the request is not made on the port 80, but 8088
  • Again, Nginx sees a server block with that port: your API
  • It does what he does well: reverse proxying to fastcgi_pass php:9000;

And we are done!


I don't specifically know Nextjs. You might need to proxy additional headers to your node app, but that could be totally fine for development purposes with proxy_pass node:3000 only.

There is a lot of "might happen problems" that could occur, but the general idea is here. If you have any problems, tell me in comments.

Upvotes: 2

Related Questions