Permission denied when writing to local directory, even after factory rebuild

Directories within Spaces where users have write permissions are quite limited. Directories starting with a period (.) or those directly under the root directory are almost impossible. They may also be inaccessible. It’s best to assume you can’t use directories outside the user home directory. (There are some exceptions as noted below…)


Root cause: your code tries to create ./hf_cache under the app repo at runtime. In Spaces the runtime user often cannot write to the repo/workdir. Use a writable absolute path: /data if you enabled Persistent storage, else /tmp. Redirect Hugging Face caches or pass cache_dir to hf_hub_download. This is a permissions/layout issue, not hardware. (Hugging Face)

What’s happening (background + context)

  • Spaces storage layout. Your code runs inside a container. Persistent disk, when enabled, is mounted at /data and is writable. Other paths may be read-only or owned by a different user. (Hugging Face)

  • Runtime user. Docker Spaces run as UID 1000. If your repo files are owned by root or your code writes relative to . (repo checkout), writes can fail with [Errno 13]. (Hugging Face)

  • HF caching defaults. huggingface_hub and related libs write under ~/.cache/huggingface unless you override with HF_HOME, HF_HUB_CACHE, TRANSFORMERS_CACHE, etc., or pass cache_dir to hf_hub_download. If that default resolves to an unwritable place, you get a PermissionError. (Hugging Face)

  • Seen in the wild. Multiple forum threads report identical PermissionErrors in Spaces fixed by pointing caches to a writable dir like /data or /tmp. Dates: 2023-07-13, 2024-05-19, 2025-03-22. (Hugging Face Forums)

Fast, safe fix (no Docker changes)

  1. Enable Persistent storage in your Space settings. This gives you /data that survives restarts. If you skip this, use /tmp which is ephemeral. (Hugging Face)

  2. Set env vars in the Space → Settings → Variables so all HF libs write under /data:


HF_HOME=/data/.huggingface

HF_HUB_CACHE=/data/.cache/huggingface/hub

TRANSFORMERS_CACHE=/data/.cache/huggingface/transformers

HF_DATASETS_CACHE=/data/.cache/huggingface/datasets

These are the officially supported knobs. They redirect all downloads, snapshots, and token files. (Hugging Face)

  1. Change your code to stop writing ./hf_cache. Use an absolute, writable cache root, prefer /data, fall back to /tmp:

# refs:

# - HF env vars: https://huggingface.co/docs/huggingface_hub/en/package_reference/environment_variables

# - Cache dir param: https://huggingface.co/docs/huggingface_hub/en/guides/download

import os, pathlib

from huggingface_hub import hf_hub_download

# honor env if provided; otherwise choose a writable default

cache_root = (

os.getenv("HF_HUB_CACHE")

or ("/data/hf_cache" if os.path.isdir("/data") else "/tmp/hf_cache")

)

pathlib.Path(cache_root).mkdir(parents=True, exist_ok=True)

model_path = hf_hub_download(

repo_id="your/repo",

filename="yourfile.bin",

cache_dir=cache_root, # absolute and writable

)

Passing cache_dir is supported and recommended when you control the path. (Hugging Face)

  1. Defer downloads until app startup instead of at import time. Your stack trace shows a download during module import. Failures there kill the process before the server starts. Do the download in an app “startup” hook or first-request path. (Hugging Face)

  2. Quick sanity check to verify writability:


import os, pathlib

p = pathlib.Path(os.getenv("HF_HUB_CACHE", "/data/.cache/huggingface/hub"))

print(p, "exists:", p.exists(), "writable:", os.access(p, os.W_OK))

If writable is False, switch to /tmp/... or fix Docker user/ownership. (Hugging Face)

If you are using a Docker Space

Match the runtime user and set caches to /data. This avoids permission flips between build-time root and run-time UID 1000.


# refs:

# - Docker Spaces permissions: https://huggingface.co/docs/hub/en/spaces-sdks-docker

# - First Docker Space guide: https://huggingface.co/docs/hub/en/spaces-sdks-docker-first-demo

RUN useradd -m -u 1000 user

USER user

ENV HOME=/home/user PATH=/home/user/.local/bin:$PATH

WORKDIR $HOME/app

COPY --chown=user . $HOME/app

# put HF caches on the persistent volume if enabled

ENV HF_HOME=/data/.huggingface \

HF_HUB_CACHE=/data/.cache/huggingface/hub \

TRANSFORMERS_CACHE=/data/.cache/huggingface/transformers

These lines mirror the official guidance and fix typical [Errno 13] cache errors. (Hugging Face)

Why this solves it

  • /data is the documented persistent, writable mount for Spaces. Redirecting caches there removes permission failures and keeps downloads across restarts. (Hugging Face)

  • hf_hub_download(cache_dir=...) and HF_* env vars are the supported ways to move caches. They exist for containerized runtimes like Spaces. (Hugging Face)

  • UID 1000 at runtime plus root-owned files during build causes write failures unless you set the user and ownership properly. The docs warn about this. (Hugging Face)

Common pitfalls to avoid

  • Writing under . or ~ without confirming ownership. Use absolute paths. (Hugging Face)

  • Assuming caches persist in /tmp. They vanish on restart. Prefer /data if you need persistence. (Hugging Face)

  • Changing file permissions to 777 in containers. This can mask the real problem and still fail under UID 1000 on redeploy. Move caches instead. Background discussion exists, but official guidance is to set caches and user. (Stack Overflow)

Short reference set (checked Oct 11, 2025)

  • Spaces persistent storage: /data mount, persists across restarts. (Hugging Face)

  • HF cache control: HF_HOME, HF_HUB_CACHE, cache_dir in hf_hub_download. (Hugging Face)

  • Docker Spaces permissions: runtime UID 1000, set user, WORKDIR, ownership. (Hugging Face)

  • Similar issues and fixes: forum threads confirming permission-denied symptoms and /data or env-var fixes. (Hugging Face Forums)

If you enable storage and apply the env vars, your current PermissionError: './hf_cache' will stop. If you want, share the exact repo snippet where hf_hub_download is called and I’ll show the minimal diff to adopt /data or /tmp.