Docker configuration with Nginx routing for Vue and Laravel
Tags: docker, nginx, laravel, vue
Published: 2021-11-29 20:19:50

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0l6co6feer3gz9p7pak.png)

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
```

*frontend-code* (with Vue structure)
```
/node_modules 
/src
/public
- nginx.conf
- Dockerfile
```

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;
  }
}
```

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
```

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"]
```

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;
  }
}
```

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
```

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
```

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