DEV Community

Philip Perry
Philip Perry

Posted on

Docker configuration with Nginx routing for Vue and Laravel

In my previous post I wrote how to use a different port to have frontend communicate with backend, but the problem with that was that I wasn't able to setup SSL easily. I actually just have Clouflare handle encryption, but it requires that both frontend and backend run on port 80 and I didn't want to use a separate domain for the backend. Thankfully, Nginx can handle the routing based on the path. Basically, I setup Nginx to route everything in the url to the Vue frontend container (running Node), unless the path starts with /api in which case it gets handled by the Laravel backend. You will notice that the Docker containers themselves run on different ports (3000 and 9000 in this case), but only Nginx communicates with them.

Image description

I'm not going to explain everything in here, as there are other articles that do it better. I'm just documenting my settings, as I wasn't able to find any PHP/Laravel tutorial on this.

My folder/file structure:

backend-code (with Laravel structure)

/app
/public
/resources
/ nginx
  - default.conf
  - Dockerfile
- docker-compose.yml
- Dockerfile
Enter fullscreen mode Exit fullscreen mode

frontend-code (with Vue structure)

/node_modules 
/src
/public
- nginx.conf
- Dockerfile
Enter fullscreen mode Exit fullscreen mode

This is the Nginx config file
backend-code/nginx/default.conf:

upstream frontend {
  server frontend:3000;
}

upstream backend {
  server backend:9000;
}

server {
  listen 80;
  index index.php index.html;
  error_log  /var/log/nginx/error.log;
  access_log /var/log/nginx/access.log;
  root /var/www/html/public;

  location / {
      proxy_pass http://frontend;
  }

  location /api {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
  }

  # Nginx Pass requests to PHP-FPM
  location ~ \.php$ {
      #try_files $uri =404;
      fastcgi_split_path_info ^(.+\.php)(/.+)$;
      fastcgi_pass backend;
      fastcgi_index index.php;
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PATH_INFO $fastcgi_path_info;
  }
}
Enter fullscreen mode Exit fullscreen mode

For doing the routing with the frontend, we can simply do proxy_pass http://frontend, but for our PHP-FPM backend, we need to do fastcgi_pass backend;

backend-code/nginx/Dockerfile:

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf
Enter fullscreen mode Exit fullscreen mode

PHP (Backend) Dockerfile:

FROM php:7.4-fpm

# Copy composer.lock and composer.json into the working directory
COPY composer.lock composer.json /var/www/html/

# Set working directory
WORKDIR /var/www/html/

# Install dependencies for the operating system software
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    zip \
    vim \
    git \
    curl

# Install extensions for php
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install gd

# Install composer (php package manager)
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Copy existing application directory contents to the working directory
COPY . /var/www/html

# Assign permissions of the working directory to the www-data user
RUN chown -R www-data:www-data \
    /var/www/html/storage \
    /var/www/html/bootstrap/cache

# Expose port 9000 and start php-fpm server (for FastCGI Process Manager)
EXPOSE 9000
CMD ["php-fpm"]
Enter fullscreen mode Exit fullscreen mode

Frontend (Vue):

Notice that we are running 2 Nginx containers (the other one serves also as proxy, while this one is just for serving the frontend):

frontend-code/nginx.conf

server {
  listen 3000;

  location / {
    root /app;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
  }
}
Enter fullscreen mode Exit fullscreen mode

frontend-code/Dockerfile:

FROM node:16 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY ./ .
RUN npm run build

FROM nginx as production-stage
EXPOSE 3000
RUN mkdir /app
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build-stage /app/dist /app
Enter fullscreen mode Exit fullscreen mode

And finally the docker-compose.yml which I put inside the backend folder for simplicity:

backend-code/docker-compose.yml

version: "2.0"
services:
  #PHP Service
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    image: cloudsigma.com/php
    container_name: backend
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: backend
      SERVICE_TAGS: dev
    working_dir: /var/www/html/
    volumes:
      - ./:/var/www/html/
      - ./php/laravel.ini:/usr/local/etc/php/conf.d/laravel.ini
    networks:
      - app-network

  frontend:
    stdin_open: true
    container_name: frontend
    build:
      context: ../frontend-code
      dockerfile: Dockerfile
    volumes:
      - /app/node_modules
      - ../frontend-code:/var/www/html/
    networks:
      - app-network

  #Nginx Service
  webserver:
    depends_on:
      - backend
      - frontend
    build:
      context: ./nginx
      dockerfile: Dockerfile
    container_name: webserver
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
    networks:
      - app-network

#Docker Networks
networks:
  app-network:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

If there are things that can be improved with this setup or if you have questions, feel free to post a comment.

Latest comments (7)

Collapse
 
jaaf profile image
jaaf

Hello Phillip. Thank you very much for this article. It is close to what I am looking for.
Unfortunately when I visit the localhost I get a blank page.
Here is what I did.
I created a new laravel application with
laravel new dockertest
cd dockertest
I then created a vue app with
npm init vite vue
I renamed the vue folder to frontend-code and created a backend-code folder in which I put the laravel app.
I tested the backend with
php artisan serve
and the frontend with
npm install && npm run dev
Both worked fine. I was a bit surprised to see that the port for the frontend server was 5173 and not the 3000 I was accustomed to.
I stopped both servers.
Then I replicated the structure you define adding the files you provide.
I cded in backend-code and run
docker-compose up -d.
Visiting the localhost page
I had a blank page and only 4 requests done, the last one being for the favicon.ico
Image description
I did the same with a more advanced application and the result was roughly the same.
Could you help me?

Collapse
 
codeperl profile image
mohammad ahsrafuddin ferdousi • Edited

Hi,

Nicely written! I have a bit different scenario. If you can help me with that I will be glad.

I have one container for nuxt app. This "nuxt-app" is ssr runs in port 3001 and running behind nginx with "proxy_pass". This nginx container name is "nuxt-nginx". "nuxt-nginx" runs at port 443 at guest and from host port 4430. So, the url is laraecom.com:4430.

I have another container for laravel api. This "laravel-api" is running behind nginx too. This nginx container name is "laravel-api-nginx". "laravel-api-nginx" runs at port 443 at guest and from host port 443. So, the url is api.laraecom.com.

From "nuxt-app", laraecom.com:4430 (which use nginx port 443 to run at port 3001 actually.) I am calling api (api.laraecom.com/api/v1) with axios. it also use "auth-next" and "proxy" for axios. This is my gist for nuxt.config.js.

gist.github.com/codeperl/5ab6d0aaf...

Application dockrized successfully. The nuxt app is running successfully too. But when I am trying to login via axios. It shows "axios: Network error"!

I am stuck here for last 3-5 days and can't move forward!

Any help is really appreciated!

Collapse
 
m0s4ysh3ll0 profile image
Moritz Büttner • Edited

Hello Philip! First of all: Thank you for this good article! It was exactly what I was looking for! :)
I was wondering if it is somehow possible to configure nginx to also allow api requests from outside the container network. When I 'docker exec' into the webserver container, I can call my laravel api with backend:9000/api/test, but from outside the container (eg. with Postman) I get 502 error back from nginx. I was hoping to do it like localhost/api/test, but no success so far.

I'm looking forward to your answer and thanks in advance!

Kind regards,
Mo

Collapse
 
programmingdecoded profile image
Philip Perry

Hi Mo, Sorry for the late answer. Yes, localhost/api/test should work. Is there anything in the Docker logs of your container that could give you a clue as to what went wrong?

Collapse
 
m0s4ysh3ll0 profile image
Moritz Büttner

I figured it out, just forgot to write it here in the comments... :) thank you!

Collapse
 
homeynow profile image
homeynow

Hello Philip,
i tried this one and i am getting this error. Can you help me?
Step 2/11 : COPY composer.lock composer.json /var/www/html/
COPY failed: file not found in build context or excluded by .dockerignore: stat composer.lock: file does not exist

thank you

Collapse
 
dlgombert profile image
Daniel Gombert

Hay homeynow - this is because the file is copied over before composer install. In other words, you don't have a composer.lock file yet. There's probably a better way to do this, but for now on the host side, just create a file called composer.lock in your backend-code container. That will fix this.