Laravel & Docker - The stream or file \"/var/www/html/storage/logs/laravel.log\" could not be opened in append mode: Failed to open stream

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel & Docker: Solving the Dreaded "Permission Denied" Log File Error

As a seasoned developer, I’ve seen countless times how the transition from local development to containerized environments introduces frustrating roadblocks. One of the most common—and most annoying—errors developers face when setting up Laravel applications in Docker is exactly what you encountered: the log file permission denial.

The error message, The stream or file "/var/www/html/storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied, is fundamentally a Linux file system issue masked by Docker's volume mounting mechanism. It tells us that while the container sees the directory /var/www/html, the user process running inside the PHP-FPM container does not have the necessary write permissions to create or append to files within that mounted volume on the host machine.

This post will diagnose why this happens in your specific setup and provide a robust, production-ready solution using best practices for managing file permissions in Docker.

The Root Cause: Volume Mounting and UID Mismatch

When you use Docker volumes (the -v flag or the volumes: section in docker-compose), you are linking a directory on your host machine directly into the container's filesystem. While this is fantastic for development speed, it requires careful management of ownership and permissions.

The conflict arises because:

  1. Host Permissions: Your host machine (e.g., running Linux or macOS) has specific User IDs (UIDs) and Group IDs (GIDs).
  2. Container Permissions: The application running inside the container (in your case, PHP-FPM) runs as a specific user (often www-data or a custom user like you defined), which has its own internal UID/GID mapping.

If the UID/GID of the process inside the container does not match the expected permissions on the mounted host volume, the operation fails with "Permission denied."

Fixing the Permissions: A Multi-Layered Approach

The solution requires synchronizing the user identity between the host and the container build process. Simply setting the user inside the Dockerfile is often insufficient; we need to ensure that the files created by the container belong to a user that has write access on the host volume.

Here is how we refine your setup to resolve this:

Step 1: Standardizing User Management in Your Dockerfile

Your approach of creating a non-root user (laravel) and attempting to adjust group settings is good, but we need to enforce ownership explicitly. Instead of relying solely on sed commands within the build process, let's focus on making sure the container environment matches the host expectations.

We will leverage the fact that when you mount volumes, the permissions often default to matching the host user if the container process is run as a user whose ID matches the host's group context.

Step 2: Implementing Explicit Ownership (The Robust Fix)

Instead of just mounting the directory, we can ensure that the volume itself is accessible by the intended service user within the container. While managing UID mapping across different OSes can be complex, for simple setups, ensuring the base user running FPM has appropriate rights to the mounted path is key.

In your php.dockerfile, let's refine the permissions management. We will ensure that when the application starts, it operates within a context where it can write logs without hitting permission walls.

Refined php.dockerfile Example:

FROM php:8-fpm-alpine

# Set environment variables for consistency
ENV PHPGROUP=laravel
ENV PHPUSER=laravel

# Create the user and group
RUN addgroup -g ${PHPGROUP} ${PHPGROUP} \
    && adduser -u 1000 -g ${PHPGROUP} -s /bin/sh -D ${PHPUSER}

# Ensure the application directory exists and is owned by the new user
RUN mkdir -p /var/www/html/public /var/www/html/storage \
    && chown -R ${PHPUSER}:${PHPGROUP} /var/www/html

# Set permissions for the FPM service to ensure it can write logs
RUN sed -i "s/user = www-data/user = ${PHPUSER}/g" /usr/local/etc/php-fpm.d/www.conf \
    && sed -i "s/group = www-data/group = ${PHPGROUP}/g" /usr/local/etc/php-fpm.d/www.conf

RUN docker-php-ext-install pdo pdo_mysql

CMD ["php-fpm", "-y", "/usr/local/etc/php-fpm.conf", "-R"]

Step 3: Reviewing docker-compose.yml

Ensure your docker-compose.yml correctly links the source code to the volume, as you have done:

# ... inside docker-compose.yml
    volumes:
      - ./src:/var/www/html  # This is where the permissions are applied
# ...

By explicitly running chown -R ${PHPUSER}:${PHPGROUP} /var/www/html during the build, we tell Docker that the directory structure inside the container should be owned by the user identity we established. When this volume is mounted, it often resolves the permission conflict with the host operating system, allowing PHP-FPM to write logs successfully to /var/www/html/storage/logs/laravel.log.

Conclusion: Building Resilient Laravel Applications

Dealing with Docker permissions is a rite of passage for every developer moving into containerization. The key takeaway is this: never assume permissions will align automatically. Always explicitly define ownership and permissions during the image build process.

When building robust applications, whether they are simple scripts or complex frameworks like Laravel (as promoted by resources like laravelcompany.com), treating the container as an isolated system that requires explicit setup is crucial for avoiding runtime errors. By mastering volume management and UID/GID synchronization, you move from debugging transient permission errors to building truly resilient, reproducible development environments. Happy coding!