1

I have a Django app build by uv running inside Docker. I mount the local filesystem as a volume in the container using Docker Compose so that edits to the source code locally trigger reloading of the app in the container. It almost works.

The issue is that the .venv directory built by uv is owned by the root user of the Docker container. This means that I cannot edit those files from my local filesystem without root access.

I have gotten around this with pip/pipenv/poetry/pdm in the past by installing the venv as a non-root user who has the same uid and guid as my local user (those values are passed into Docker via a .env file). But I can't work out how to do that for uv.

Dockerfile:

FROM python:3.12-slim-trixie

# create non-root user
RUN addgroup --system appuser && adduser --system --group --home /home/appuser appuser


# set work directory
WORKDIR /app

# environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    UV_LINK_MODE=copy \
    UV_PYTHON_DOWNLOADS=never \
    UV_PROJECT_ENVIRONMENT=$APP_HOME/.venv

# install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# install system dependencies
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
    build-essential netcat-traditional \
    python-is-python3 python3-gdal python3-psycopg2

# switch to app user [THIS MAKES THE NEXT COMMAND FAIL]
# USER appuser

# synchronise project dependencies
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache \
    uv sync --all-groups --frozen --no-install-project 

# run entrypoint script
COPY ./entrypoint.sh .
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]

docker-compose.yml:

services:
   server:
    build: 
      context: .
    command: uv run manage.py runserver 0.0.0.0:8000
    tty: true
    environment:
      DJANGO_SETTINGS_MODULE: config.settings
    volumes:
     - ./:/app/
    ports:
      - "8000:8000"
    env_file:
      - .env

entrypoint.sh:

#!/bin/sh
set -euo pipefail

cd /app

# ensure "app" user in the container has same ids as local user outside the container
if [ ! -z ${RUN_AS_UID} ]; then usermod --uid $RUN_AS_UID appuser; fi
if [ ! -z ${RUN_AS_GID} ]; then groupmod --gid $RUN_AS_GID apuser; fi

# setup django
uv run ./manage.py migrate
uv run ./manage.py collectstatic --no-input --link

# run whatever command was passed to the container (from docker-compose)
exec "$@"

.env:

RUN_AS_UID=1001
RUN_AS_GID=1001
3
  • RUN sudo -u appuser uv sync ? Commented Oct 10 at 17:10
  • @J_H - Thanks. I just tried that and got error: failed to create directory /app/.venv: Permission denied (os error 13). Commented Oct 10 at 17:23
  • 2
    Well, cd someplace writable when creating the python virt environment. Or chown -R appuser /app Commented Oct 10 at 17:27

1 Answer 1

1

You may just need to fix the owner of /app after you create it, as noted in comments

RUN chown --recursive appuser:root /app

It's often a great design to go from least dependency churn to most to avoid rebuilds, and most privilege to least (as you largely do!)

  • OS-level packages (apt et al.)
  • create a user and drop privileges
  • install user-level packages
  • small local fixes like chmod

I don't have a good setup to try this right now, but streamlining and simplifying or removing caching and then adding it back later may help tremendously

FROM python:3.12-slim-trixie

ENV # ...

# install/update apt dependencies and tidy
RUN # apt-get update && apt-get install ... && apt-get clean

# install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# drop to unprivileged user
RUN useradd --create-home appuser
RUN mkdir -p /app  # has this already been created?
RUN chown --recursive appuser:root /app
WORKDIR /app
USER appuser

# copy user files (prefer whole local directory?)
COPY pyproject.toml ./  # uv lock just caching?
COPY entrypoint.sh ./
RUN chmod +x entrypoint.sh

# synchronize project dependencies
# configure caching once working!
RUN # uv sync ...

# run entrypoint script
ENTRYPOINT ["/app/entrypoint.sh"]

However, you may run into trouble later in your compose file, as you clobber /app!

What should be inside the directory during runtime? Probably the venv and structure from your code? Should your code be built at runtime or is there nothing to build and it's completely interpreted?

Consider what you want to persist or pass in with compose

  • configuration? (very likely, done with env vars)
  • venv? (unlikely, avoid clobbering .venv or install elsewhere and symlink in at runtime?)
  • user files? (likely want to persist between runs and manually clear, should this be some nested directory like /app/tmp or /tmp/app?)
  • local (host space) built or unbuilt package? (use-case dependent, such as testing)

When testing, it actually can be convenient to persist the venv in the host space and maintain it and/or make incremental changes to your package with the given command: or ENTRYPOINT, but you need to be cautious of permissions

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. Your advice helped a lot. In particular this line: RUN chown --recursive appuser:root /app

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.