Skip to main content
Tutorial

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

5 min read

My Docker setup for Next.js and Laravel that actually works after 8+ years as a UAE full-stack dev

DockerLaravelNext.jsLocal DevelopmentAbu Dhabi TechUAE Developers

Back in December 2024, I spent 8 hours debugging why a Laravel/Next.js app kept crashing during local builds. The culprit? A mismatched PHP version that one of my clients—a luxury car rental company in Abu Dhabi—had deployed in production. That pain led me to standardize a Docker setup that works consistently across 40+ projects I’ve shipped for UAE and GCC businesses. If you’re stuck repeating "docker compose up" just to see a white screen, this guide might save you some headaches.

Why Dockerize Both Frameworks Together?

Running Next.js and Laravel in separate containers isn’t just overengineering—it’s practical when building apps like Tawasul Limo, where real-time booking logic and SEO-driven marketing pages need to coexist. Trying to manage XAMPP for Laravel and nvm for Node.js versions across client projects became unsustainable, especially when working with UAE clients who expect tight deadlines and stable environments.

Here’s what my current workflow handles reliably:

  • Reproducing production environments exactly (PHP 8.2 for Laravel, Node 18 for Next.js)
  • Sharing code between both containers without manual file syncing
  • Persistent MySQL/MariaDB data for apps like DAS Holding’s corporate site
  • Zero config headaches during handoffs between my team and clients

Tooling: Compose File That Doesn’t Suck

My docker-compose.yml has evolved to look like this:

yaml
version: '3.8'
services:
  laravel:
    build: ./laravel
    volumes:
      - ./laravel:/var/www/html
    ports:
      - "9000:9000"
    depends_on:
      - mysql
    networks:
      - app-network

  nextjs:
    build: ./nextjs
    volumes:
      - ./nextjs:/nextjs
    ports:
      - "3000:3000"
    networks:
      - app-network

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: main_db
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
    networks:
      - app-network

networks:
  app-network:
volumes:
  mysql_data:

The key trick? Using a shared network (app-network) so the Next.js container can directly hit Laravel APIs at http://laravel:9000 in development. No more hardcoding localhost ports in API clients. I learned this the hard way while building Greeny Corner’s AI plant identification APIs, where inconsistent endpoints caused 3 hours of wasted time debugging mock data issues.

Configuring the Laravel Container

Dockerfile for Laravel:

Dockerfile
FROM php:8.2-fpm
RUN apt-get update && apt-get install -y git unzip
RUN docker-php-ext-install pdo_mysql
WORKDIR /var/www/html
COPY . /var/www/html
RUN composer install

Don’t forget to enable OPcache in production containers—but keep it off for local development. I spent a day debugging why Blade templates weren’t reloading when opcache.enable was set to 1 in the dev container. D'oh. Laravel Sail users: this setup is intentionally simpler since most of my UAE clients don’t need all the Sail extras.

For MySQL, I stick with standard 8.0 unless a project requires spatial functions from MariaDB 10.6+. One of my Dubai-based real estate clients (Reach Home Properties) needed geospatial queries for property filters, which required swapping the mysql image for mariadb and adding:

yaml
command: ['--plugin-load-add=ha_spatial.so']

Next.js Container Gotchas

Here’s what my Next.js Dockerfile looks like:

Dockerfile
FROM node:18
WORKDIR /nextjs
COPY . /nextjs
RUN npm install
EXPOSE 3000
CMD ["npm", "run", "dev"]

This works 90% of the time, but watch out for next.config.js that imports files from Laravel—like when I was handling multilingual URLs for a client’s Arabic/English site. We moved shared routing logic to a separate NPM package instead of mounting Laravel directories inside the Next container. Trust me, battling Docker volume mount paths with Webpack is no fun.

Pain Points and Workarounds

Port 3000 conflicts? That’s why I check lsof -i :3000 constantly—especially on my Mac after waking from sleep. For Laravel queue workers, use environment variables to set QUEUE_HOST instead of assuming everything’s 127.0.0.1. I blew 5 hours once debugging why horizon workers couldn’t connect to Redis when the hostname was hardcoded.

File permissions on Linux are another gotcha. When mounting Laravel code into containers, set:

yaml
volumes:
  - ./laravel:/var/www/html
  user: "1000:1000"

Otherwise, you’ll waste time fixing "Permission denied" errors from your queue or cache directories—yes, that happened on a project in April 2025, and yes, I should’ve learned earlier.

Final Workflow Tips

I use VSCode’s Dev Containers plugin for most client projects now. For instance, when building the Tawasul Limo platform, devcontainers let me install IDE-specific extensions inside Docker without bloating the base image. But I skip it for small projects where a basic docker compose setup is faster.

One thing I’ve stopped doing: building both containers in a single Compose file for personal projects. For freelance work with UAE clients expecting strict NDA compliance, isolating service containers into separate Compose files helps compartmentalize access.

Frequently Asked Questions

What's the benefit of separate containers for Next.js and Laravel?

Separating them avoids conflicting dependencies—like needing Python 2.7 for Laravel Scout vs Python 3.x for a Next.js image processor. It also mirrors production environments where these apps often scale independently, which I saw firsthand when handling traffic for Dubai Shopping Festival campaigns.

How do you handle environment variables across both containers?

.env files work fine for local dev, but I enforce a rule: never commit .env files with API keys. For staging/prod, we inject secrets via GitHub Actions. For local setups, the Laravel container shares envs with Next.js through a shared file, which gets filtered at build time depending on the container.

Why does npm install keep failing in the Next.js container?

Check if you’re using a .dockerignore that excludes package.json or node_modules. I once spent 2 hours debugging this before realizing a blanket **/node_modules in the .dockerignore was blocking proper installs. Also, verify Node versions between package.json engines and your Dockerfile.

How do you debug PHP inside Docker?

I use xdebug in the Laravel container connected to VSCode’s PHP Debug extension. The config needs xdebug.client_host=host.docker.internal and mapping local paths to container paths in the launch.json. This saved me during the launch of Greeny Corner’s AI feature, which required heavy Laravel background processing.

If you’ve tried Docker setups that never quite work, I can help you build a system that doesn’t waste your time. Get in touch or book a free consultation to talk about your next project—especially if you’re running a UAE business struggling with dev environment consistency.

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