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.
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.
Top comments (7)
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
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?
I figured it out, just forgot to write it here in the comments... :) thank you!
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
I did the same with a more advanced application and the result was roughly the same.
Could you help me?
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!
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
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.