Skip to main content
Tutorial

Docker for Next.js and Laravel: My Local Dev Setup That Actually Works

5 min read

Docker configuration for Laravel and Next.js developers in UAE: a real setup that works, from a PMP-certified developer

DockerNext.jsLaravellocal developmentAbu Dhabi developers

Last week, I was troubleshooting a Laravel/Next.js monorepo for a limo booking client in Abu Dhabi. The PHP dependencies were broken, the frontend was hitting 502s, and my Mac's local PHP version was fighting with Docker. This is why we containerize—but getting the setup right took me 3 full days and more coffee than I'm comfortable admitting. Let me walk you through what actually works.

Why Docker Makes My Life Easier (Mostly)

Three years ago, I shipped Tawasul Limo using Laravel for bookings and Next.js for the admin dashboard. Docker wasn't my first choice, but the client needed identical Dev and Prod environments to avoid the "works on my machine" chaos.

My main requirements:

  • Run Laravel (with MySQL 8.0 and Redis) and Next.js (with Node 18) side by side
  • Share code between containers (hot-reloading matters)
  • Keep environment variables DRY (shoutout to .env files)
  • Don't fight my machine's native tools (I'm looking at you, MAMP)

Docker Compose became my best friend—but only after I stopped trying to cram everything into a single docker-compose.yml.

The Folder Structure That Saves Me Hours

Here's what I do for monorepos (like Tawasul Limo) and multi-service projects:

project-root/  
├── backend/      # Laravel app  
│   ├── Dockerfile  
├── frontend/     # Next.js app    
│   ├── Dockerfile  
├── docker-compose.yml  
└── shared/       # Shared config or components  

At first, I tried mounting the whole repo as a volume—but changes in /shared wouldn't propagate cleanly. Now I bind-mount only what I need. For Laravel, that means:

yaml
volumes:
  - ./backend:/var/www/html
  - ./backend/storage:/var/www/html/storage  

Next.js required a tweak: chokidar doesn't like Docker volumes. I ended up using rsync for faster file syncing.

Dockerfiles: Keep Them Dumb, Keep Them Fast

Backend (Laravel) Dockerfile

Dockerfile
FROM php:8.2-fpm-alpine3.18  

RUN docker-php-ext-install pdo pdo_mysql  
WORKDIR /var/www/html  
COPY ./backend /var/www/html  
RUN composer install --no-dev  

Simple, but it bit me in 2024 when a Dubai client's Arabic text kept breaking. MySQL collation wasn't set correctly—lesson learned: always check utf8mb4 support in your PHP image.

Frontend (Next.js) Dockerfile

Dockerfile
FROM node:18-alpine3.18  

WORKDIR /app  
COPY ./frontend /app  
RUN npm install --production  
CMD ["npm", "run", "dev"]  

I used --no-cache for builds. Then I realized the CI server hated me for it. Now I cache node_modules unless I'm debugging package conflicts.

Docker Compose: The Glue That Holds It Together

yaml
version: '3.8'

services:  
  laravel:  
    build: ./backend  
    ports:  
      - "9000:9000"  
    volumes:  
      - ./backend:/var/www/html  
    environment:  
      - DB_HOST=mysql  

  nextjs:  
    build: ./frontend  
    ports:  
      - "3000:3000"  
    volumes:  
      - ./frontend:/app  
    depends_on:  
      - laravel  

  mysql:  
    image: mysql:8.0  
    ports:  
      - "3306:3306"  
    environment:  
      - MYSQL_ROOT_PASSWORD=secret  

Yes, I mapped MySQL's port to my host. Yes, this is a security risk. But when you're debugging Laravel DB connections in Dev (and you're not exposing this in Production—right?), it's a lifesaver.

One gotcha: PHP-FPM was crashing because of user permissions. I added a Dockerfile step to set the right user:

Dockerfile
RUN useradd -u 1000 app && chown -R app /var/www/html  
USER app  

Database and Cache Gotchas (The Stuff That Bites You Friday at 6PM)

I always include Redis in the stack, even if the client doesn't need it yet. Future-proofing.

yaml
services:  
  redis:  
    image: redis:alpine3.18  
    ports:  
      - "6379:6379"  

Once, a real estate client's staging site went down because I forgot to set REDIS_HOST=redis in Laravel's .env. Dockerized environments don't share the same networking as your machine—always double-check hostnames in config files.

Local Dev vs Production: What's Actually Different

I maintain separate .env and docker-compose files for Dev and Prod. For example:

Dev .env:

DB_HOST=mysql  
REDIS_HOST=redis  

Prod .env:

DB_HOST=aws-rds-instance.region  
REDIS_HOST=elasticache-endpoint  

I use Docker secrets in Production, but that's overkill for local dev. A quick swap of the --env-file flag in Docker run commands handles 90% of my needs.

Frequently Asked Questions

Should I run MySQL in the same Docker setup as my app containers?

Yes, unless you're optimizing for DevOps purity. I've killed local MySQL processes too many times mid-sprint—dockerizing the DB gives me clean state resets without breaking other projects.

How do I debug a Laravel app running in Docker?

Use xdebug, but be warned: it will slow things down. I map port 9003 and set the XDEBUG_MODE=debug:

Dockerfile
ENV PHP_XDEBUG_REMOTE_PORT=9003  

Does Docker slow down Next.js hot-reloading on an M1 Mac?

It did in 2023. Switched to docker context use desktop-linux, and it's better now. Still not as fast as native Node, but acceptable for client demos.

How to handle NPM packages that require Python/Rust in Docker builds?

Use multi-stage builds. First, install system dependencies in your Dockerfile (RUN apk add --no-cache python3 py3-pip), then COPY --from=build_stage. Painful? Yes. Life-threatening? No.

Want more concrete Docker tips for PHP/JS projects? Drop me a note or book a free 30-minute session. I've spent 7+ years wrestling with Docker setups for UAE startups and enterprise clients—let's skip the trial and error.

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah