Here I want to publish a simple yet functional setup for a dockerized app with Laravel backend and Vue.JS frontend.

What did I want?

  • Simple setup for development with docker-compose
  • With Laravel acting as pure backend
  • XDebug
  • Vue to take over all the frontend needs
  • Hot reload for the Vue app
  • No SSL in local development environment

What did I come up with?

  1. Nginx acting as web trafic router, proxying to frontend or backend, depending on request URI
  2. Backend service: Laravel running artisan serve
  3. Frontend service: Vue-cli running npm run serve

Directory/repository structure

Legend: d: directory, f: file

d: backend -> git submodule, points to laravel API repo
d: frontend -> git submodule, points to Vue app repo
d: etc
  d: nginx
    d: conf.d
      f: default.conf.nginx
  d: php
    f: .gitignore
d: dockerize
  d: backend
    f: Dockerfile
f: docker-compose.yml
f: Makefile

As you can see, this project itself mainly serves as an infrastructure project which connects our services and glues them all together. Developer may clone a single repo and work in it, if that’s sufficient. Normally, though, everyone would want to clone this infra repo and start docker-compose stack to have all the project services running locally.

Nginx router

docker-compose part:

  image: nginx:alpine
    - ./etc/nginx/conf.d/default.conf.nginx:/etc/nginx/conf.d/default.conf
    - 80:80
    - backend
    - frontend

…and the configuration, of course.

server {
    listen 80;
    server_name frontend;

    error_log  /var/log/nginx/error.log debug;

    location / {
        proxy_pass http://frontend:8080;

    location /sockjs-node {
        proxy_pass http://frontend:8080;
        proxy_set_header Host $host;
        # below lines make ws://localhost/sockjs-node/... URLs work, enabling hot-reload
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

    location /api/ {
        # on the backend side, the request URI will _NOT_ contain the /api prefix,
        # which is what we want for a pure-api project
        proxy_pass http://backend:8080/;
        proxy_set_header Host localhost;


I added a Dockerfile for the backend, because official PHP images don’t include XDebug. Also, later on, I wanted to add redis extension:

FROM php:fpm-alpine

RUN apk add --no-cache $PHPIZE_DEPS oniguruma-dev libzip-dev curl-dev \
    && docker-php-ext-install pdo_mysql mbstring zip curl \
    && pecl install xdebug redis \
    && docker-php-ext-enable xdebug redis

RUN mkdir /app

CMD php artisan serve --host= --port=8080

And of course, we build this image using docker-compose, that is, we automate and simultaneously document this process with a declarative approach:

    context: dockerize/backend
  # this way container interacts with host on behalf of current user.
  # !!! NOTE: $UID is a _shell_ variable, not an environment variable!
  # To make it available as a shell var, make sure you have this in your ~/.bashrc (./.zshrc etc):
  # export UID="$UID"
  user: ${UID}:${UID}
    - ./backend:/app
    # custom adjustments to php.ini
    # i. e. "xdebug.remote_host" to debug the dockerized app
    - ./etc/php:/usr/local/etc/php/local.conf.d/
    # add our custom config files for the php to scan
    PHP_INI_SCAN_DIR: "/usr/local/etc/php/conf.d/:/usr/local/etc/php/local.conf.d/"
  command: "php artisan serve --host= --port=8080"

The laravel app itself is beyond the scope of this post, I will simply assume it’s a standard app built with composer create-project laravel/laravel or alike.

Take a note of the comment above the user: ${UID} directive


Again, the Vue app itself is out of scope. I created mine with @vue/cli, using the vue-cli-service.


  image: node:current-alpine
  user: ${UID}:${UID}
  working_dir: /home/node/app
  - ./frontend:/home/node/app
    NODE_ENV: development
  command: "npm run serve"