PermissionError: [Errno 13] Permission denied: '/app/data/report.csv'

Python · updated 2026-11-04

Raised by open(), os and shutil calls, and socket binds when the operating system refuses the operation for the user the process runs as. The path in the message is the one that was refused; it is often a volume, a log or upload folder, or a cache directory, and it usually works on a laptop where the code runs as you.

Traceback (most recent call last):
  File "/app/jobs/export.py", line 27, in write_report
    with open(path, "w") as f:
         ^^^^^^^^^^^^^^^
PermissionError: [Errno 13] Permission denied: '/app/data/report.csv'

Quick fix

Check which user the process runs as (id inside the container) and who owns the path (ls -ld). In Docker, chown the directory to the app user in the Dockerfile, or mount the volume with matching ownership. Write to a directory the app owns (or tempfile) rather than the code directory. If the path is a directory, open a file inside it instead.

  1. Run id and ls -ld <path> where the process runs (docker exec into the container) to compare the user and the owner.
  2. Docker: create the directory and chown it to the app user before the USER line, or fix ownership on the mounted volume.
  3. Write runtime files to a writable location (a data directory you own, /tmp, tempfile) instead of next to the code.
  4. If the path is a directory (common with os.path.join mistakes), open a file inside it.
  5. Binding port 80 or 443 as non-root fails the same way on Linux: listen on 8000 and put nginx or the platform router in front.
dockerfile
FROM python:3.12-slim
RUN useradd --create-home app
WORKDIR /app
COPY --chown=app:app . .
# Directories the app writes to, owned by the app user
RUN mkdir -p /app/data /app/logs && chown app:app /app/data /app/logs
USER app
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]

Why it happens

Non-root container on root-owned files

The image switches to a non-root USER, but the directory it writes to was created or copied as root, or a volume is mounted with root ownership.

Writing inside the code directory

Read-only deploys and platforms that mount the app as read-only refuse writes next to the code; caches and uploads need a separate writable path.

A directory where a file was expected

open() on a directory raises IsADirectoryError on Linux and PermissionError on Windows, often from a path join that dropped the filename.

Low port or locked file

Binding a port below 1024 as a normal user on Linux, or opening a file another program has locked on Windows, also ends here.

Other fixes

  • Prefer the platform data directory or a mounted volume for anything written at runtime, and keep the code read-only.
  • Avoid chmod 777 as a fix: set the right owner instead.

Same error, other wording

  • PermissionError: [Errno 13] Permission denied (socket bind on port 80)
  • OSError: [Errno 30] Read-only file system: '/app/cache'
  • IsADirectoryError: [Errno 21] Is a directory: '/app/data'

Let your coding agent catch the next one

Runheed records this error in production with the stack trace, the breadcrumbs before it and the agent session that shipped the code, and serves it to Claude Code, Cursor or any MCP client. With it connected, you can ask:

> Find the PermissionError Errno 13 errors in production, tell me which path and which user, and fix it by writing to a directory the app user owns (Dockerfile chown or a different path), not by loosening permissions.

Plans from $29/moSee what your agent readsSee the setupCancel any time from the billing portal

Related errors

More in Memory, async and the process

References: Python docs: built-in exceptions (PermissionError, OSError), Dockerfile reference: USER