Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Marchius's avatar

Laravel Octane + Nginx proxy within Docker containers

I don't quite understand - is it an anti-pattern to dockerize Laravel applications in production? 95% of the guides I could find on Google are for local setup, which is different from what a production should be (The mounted app volume for starters), the other 5% use some third-party containers. I'm trying to run my Laravel application container, which would serve the app using Octane and then have another container for Nginx, which would act as a reverse proxy. Here is my Dockerfile (I'm quite new to Docker so I'm using the FPM image just because I need bash commands to work and this image seems to enable me to do that).

FROM php:8.0-fpm

# Arguments defined in docker-compose.yml
ARG user
ARG uid

COPY . /var/www

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip \
    libz-dev \
    libgd-dev \
    libmemcached-dev \
    memcached \
    libmemcached-tools \
    && apt-get install -y libmagickwand-dev \
    # use pecl to install extensions
    && pecl install imagick \
    && pecl install memcached \
    && pecl install swoole \
    # Enable installed extensions
    && docker-php-ext-enable imagick \
    && docker-php-ext-enable memcached

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-configure gd --enable-gd --with-freetype --with-jpeg
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd sockets

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN useradd -G www-data,root -u $uid -d /home/$user $user

# Create system user to run Composer and Artisan Commands
RUN mkdir -p /home/$user/.composer && \
    chown -R $user:$user /home/$user

COPY --chown=$user:$user . /var/www

# Set working directory
WORKDIR /var/www
RUN composer install


USER $user
# RUN echo "0" | php artisan octane:install
ENTRYPOINT ["php", "artisan", "octane:start"]

Here's my docker-compose.yml file

version: "3.7"
services:
  app:
    build:
      args:
        user: usersite
        uid: 1001
      context: ./
      dockerfile: Dockerfile
    image: usersite-api
    container_name: usersite-api
    restart: unless-stopped
    working_dir: /var/www
    volumes:
      - ./docker-compose/php/local.ini:/usr/local/etc/php/conf.d/local.ini
    networks:
      - usersite
  nginx:
    image: nginx:1.25-alpine
    container_name: usersite-api-nginx
    restart: unless-stopped
    depends_on:
      - app
    ports:
      - 81:80
    volumes:
      - ./:/var/www
      - ./docker-compose/nginx:/etc/nginx/conf.d
    networks:
      - usersite

networks:
  usersite:
    driver: bridge

And here's my Nginx.conf file

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

upstream app_container {
    server app:8000;
}
 
server {
    listen 80;
    listen [::]:80;
    server_tokens off;
    root /var/www/public;
 
    index index.php;
 
    charset utf-8;
 
    location /index.php {
        try_files /not_exists @octane;
    }
 
    location / {
        try_files $uri $uri/ @octane;
    }
 
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
 
    access_log off;
    error_log  /var/log/nginx/error.log error;
 
    error_page 404 /index.php;
 
    location @octane {
        set $suffix "";
 
        if ($uri = /index.php) {
            set $suffix ?$query_string;
        }
 
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
 
        proxy_pass http://app_container$suffix;
    }
}

To my understanding, everything is in order. However, when I try to access localhost:81 I get a 502 error from Nginx and when I check Nginx container logs, I see connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.0.1, server: , request: "GET / HTTP/1.1", upstream: "http://192.168.0.2:8000/?", host: "localhost:81"

Perhaps someone could guide me in the right direction?

1 like
5 replies
LaryAI's avatar
Level 58

It seems like your Nginx is not able to connect to your Laravel application running with Octane. This could be due to several reasons, but let's go through a checklist to ensure everything is set up correctly.

  1. Ensure Octane is running on port 8000: By default, Laravel Octane runs on port 8000, but you should verify this by checking the Octane configuration or the output when it starts.

  2. Docker Networking: Since you are using Docker's bridge network, make sure that the containers can communicate with each other. You can test this by entering the Nginx container and using curl to access the app container:

    docker exec -it usersite-api-nginx sh
    curl http://app:8000
    

    If you get a response, then networking is not the issue.

  3. Nginx Configuration: Your Nginx configuration seems to be correct, but double-check that the upstream app_container is pointing to the correct service name defined in your docker-compose.yml.

  4. Volumes: You are mounting the entire codebase into the Nginx container. This is unnecessary since Nginx only needs access to the public directory. Adjust your volumes to something like this:

    volumes:
      - ./public:/var/www/public
      - ./docker-compose/nginx:/etc/nginx/conf.d
    
  5. File Permissions: Ensure that the files within the container are accessible by the user that the Nginx process is running as.

  6. Dockerfile: Your Dockerfile is installing dependencies and setting up the environment. Make sure that the ENTRYPOINT is correctly starting Octane and that there are no errors during the container startup.

  7. Logs: Check the logs of both the Laravel application and Nginx for any errors that could give you more information about the issue.

  8. Port Exposure: Ensure that the port Octane is running on (8000) is exposed in your app service in docker-compose.yml. You can do this by adding the expose directive:

    services:
      app:
        # ... other configurations ...
        expose:
          - "8000"
    

If after going through this checklist you still encounter issues, you might want to simplify your setup and get it running without Docker first to ensure that there are no application-level issues. Once you have confirmed that, you can then dockerize the application again.

Here's a simplified docker-compose.yml example that might help:

version: "3.7"
services:
  app:
    build:
      args:
        user: usersite
        uid: 1001
      context: ./
      dockerfile: Dockerfile
    image: usersite-api
    container_name: usersite-api
    restart: unless-stopped
    working_dir: /var/www
    expose:
      - "8000"
    networks:
      - usersite
  nginx:
    image: nginx:1.25-alpine
    container_name: usersite-api-nginx
    restart: unless-stopped
    depends_on:
      - app
    ports:
      - 81:80
    volumes:
      - ./public:/var/www/public
      - ./docker-compose/nginx:/etc/nginx/conf.d
    networks:
      - usersite

networks:
  usersite:
    driver: bridge

Remember to replace ./public with the correct path to your Laravel application's public directory.

Marchius's avatar

I have inspected the network on which both containers are on.

[
    {
        "Name": "usersite_usersite",
        "Id": "63a0262a75648e53cf29d5353aa7d1b12ffbc7b5f7f5198a1c62aa43e14968fd",
        "Created": "2024-03-06T11:53:01.424476452Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "192.168.48.0/20",
                    "Gateway": "192.168.48.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3abaffbaff34dfe92260b65626a59197cd77ca1b1acd10ca76e5d563535cb07e": {
                "Name": "usersite-api-nginx",
                "EndpointID": "6905e822d1e36d15ee3b6c7d7d5e33cd179564848e85a907134e478c222539b0",
                "MacAddress": "02:42:c0:a8:30:03",
                "IPv4Address": "192.168.48.3/20",
                "IPv6Address": ""
            },
            "7ff6621746d75f76b314f2764d417ab8dffe41f36fbc846697df8c66d968b5ac": {
                "Name": "usersite-api",
                "EndpointID": "0656a736c5893dca12176ac3992ea2fef92479bf4b6ffce58bd56d8f64a3e971",
                "MacAddress": "02:42:c0:a8:30:02",
                "IPv4Address": "192.168.48.2/20",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "usersite",
            "com.docker.compose.project": "usersite",
            "com.docker.compose.version": "2.19.1"
        }
    }
]

I can verify that command curl http://app:8000 fails. And so does docker exec usersite-api-nginx curl 192.168.48.2:8000

I can also confirm that Octane server within usersite-api container is up and running. docker logs usersite-api output:

  INFO  Server running…

  Local: http://127.0.0.1:8000

  Press Ctrl+C to stop the server
robamos's avatar

Hey @marchius, I had this as well, I'm not sure if you got it working but I changed my Dockerfile from using the fpm image to the normal one. i.e. FROM php:8.0 and added the following command to my docker compose for the app container, instead of having it in the Dockerfile like you have above

    command: [ "php", "artisan", "octane:start", "--host=0.0.0.0", "--port=8000" ]

That seemed to do it for me, I can share my setup if that helps.

Marchius's avatar

@robamos Thanks for sharing! I will try this as I was unable to launch it, I just ditched the idea. The full Dockerfile would be awesome. I'm struggling to find quality Docker files for production Laravel setup online.

navisoft's avatar

You were missing ports for the app service.

ports:
      - 8000:8000

Please or to participate in this conversation.