diff --git a/Dockerfile b/Dockerfile index 6e98000..9d3b261 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,10 @@ FROM ubuntu:noble +# Allow the caller to specify the host UID/GID so files created by the +# container match host ownership when volumes are mounted. Defaults to 1000. +ARG HOST_UID=1000 +ARG HOST_GID=1000 + RUN apt-get -q update \ && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ ca-certificates curl gnupg \ @@ -47,4 +52,33 @@ WORKDIR /server COPY /Dockerfile /Dockerfile COPY /.env /.env +# Create a host-mapped user and group so container files are written with +# the host UID/GID. Do this late in the build so earlier root-only build +# steps are unaffected. Then ensure server and client directories are +# owned by that user and switch to it for runtime. +## Create a group with the requested GID if it doesn't exist, otherwise reuse +RUN if ! getent group ${HOST_GID} >/dev/null 2>&1; then \ + groupadd -g ${HOST_GID} hostgroup; \ + else \ + EXISTING=$(getent group ${HOST_GID} | cut -d: -f1) && echo "Using existing group $EXISTING for GID ${HOST_GID}"; \ + fi + +## Create a user with the requested UID if it doesn't exist; if a user with +## that UID already exists, don't try to recreate it (we'll chown by numeric +## UID later). Create a home directory if missing. +RUN if ! getent passwd ${HOST_UID} >/dev/null 2>&1; then \ + useradd -m -u ${HOST_UID} -g ${HOST_GID} -s /bin/bash hostuser || true; \ + else \ + EXISTING_USER=$(getent passwd ${HOST_UID} | cut -d: -f1) && echo "Found existing user $EXISTING_USER with UID ${HOST_UID}"; \ + mkdir -p /home/hostuser || true; \ + fi + +## Ensure runtime dirs are owned by the numeric UID:GID if hostuser/group +## weren't created, or by the names if they were. +RUN chown -R ${HOST_UID}:${HOST_GID} /server || true +RUN chown -R ${HOST_UID}:${HOST_GID} /client || true + +ENV HOME=/home/hostuser +USER ${HOST_UID}:${HOST_GID} + CMD ["npm", "start"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 0463c02..fd7c8b3 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,10 +1,32 @@ FROM node:20-alpine -RUN apk add --no-cache sqlite +# Build args for host UID/GID to create a matching user inside the image. +ARG HOST_UID=1000 +ARG HOST_GID=1000 + +RUN apk add --no-cache sqlite shadow WORKDIR /server -# For dev, we install in container, but to speed up, perhaps copy package and install -# But since volumes mount, just run npm install in command +# Create host user/group and ensure /server is owned by them so mounted +# files appear with the desired ownership on the host. +RUN if ! getent group ${HOST_GID} >/dev/null 2>&1; then \ + addgroup -g ${HOST_GID} hostgroup; \ + else \ + echo "group for GID ${HOST_GID} already exists"; \ + fi +RUN if ! getent passwd ${HOST_UID} >/dev/null 2>&1; then \ + adduser -D -u ${HOST_UID} -G hostgroup hostuser; \ + else \ + echo "user for UID ${HOST_UID} already exists"; \ + mkdir -p /home/hostuser || true; \ + fi + +RUN chown -R ${HOST_UID}:${HOST_GID} /server || true + +ENV HOME=/home/hostuser +USER ${HOST_UID}:${HOST_GID} + +# For dev, we install in container at runtime since volumes mount. CMD ["sh", "-c", "cd /server && npm install --no-audit --no-fund --silent && npm rebuild sqlite3 && npm run start:dev"] \ No newline at end of file diff --git a/Dockerfile.test b/Dockerfile.test index 1318791..f8d0243 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,5 +1,10 @@ FROM node:20-bullseye +# Allow host UID/GID to be provided so test artifacts are created with +# matching ownership when the workspace volume is mounted. +ARG HOST_UID=1000 +ARG HOST_GID=1000 + # Install Chromium and related deps at image build time so test runs are fast RUN apt-get update \ && apt-get install -y --no-install-recommends chromium ca-certificates fonts-liberation curl \ @@ -23,5 +28,24 @@ COPY tools/puppeteer-test/ /opt/puppeteer-test/ WORKDIR /opt/puppeteer-test +## Create a host user so files created during test runs have the host UID/GID +RUN if ! getent group ${HOST_GID} >/dev/null 2>&1; then \ + groupadd -g ${HOST_GID} hostgroup; \ + else \ + echo "group for GID ${HOST_GID} already exists"; \ + fi + +RUN if ! getent passwd ${HOST_UID} >/dev/null 2>&1; then \ + useradd -m -u ${HOST_UID} -g ${HOST_GID} -s /bin/bash hostuser; \ + else \ + echo "user for UID ${HOST_UID} already exists"; \ + mkdir -p /home/hostuser || true; \ + fi + +RUN chown -R ${HOST_UID}:${HOST_GID} /opt/puppeteer-test || true + +ENV HOME=/home/hostuser +USER ${HOST_UID}:${HOST_GID} + # Default entrypoint runs the test; the workspace is mounted by docker-compose ENTRYPOINT ["node", "test.js"] diff --git a/README.md b/README.md index 62d457b..373b5f3 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,24 @@ docker compose down --remove-orphans # Build images docker compose build + +### Ensuring container-created files match your host UID/GID + +When running containers that mount the workspace (dev/test), files created by the container may be owned by root which can be inconvenient on the host. To avoid this, images in this repo accept build-time args `HOST_UID` and `HOST_GID` and create a matching user inside the image. Set these to your current UID/GID before building so runtime-created files use your ownership. + +Example (bash): + +```bash +export HOST_UID=$(id -u) +export HOST_GID=$(id -g) +# Build images with the host UID/GID baked in +docker compose build peddlers-test peddlers-of-ketran peddlers-client peddlers-of-ketran-dev + +# Then run in dev mode as usual (hot-reload): +PRODUCTION=0 docker compose up -d --profile dev +``` + +If you prefer to set these in `.env`, add `HOST_UID=...` and `HOST_GID=...` to the file in the repo root. ``` #### Development Mode diff --git a/docker-compose.yml b/docker-compose.yml index 7e5d647..5f6761c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,9 @@ services: build: context: . dockerfile: Dockerfile + args: + - HOST_UID=${HOST_UID:-1000} + - HOST_GID=${HOST_GID:-1000} restart: always env_file: - .env @@ -23,6 +26,9 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + - HOST_UID=${HOST_UID:-1000} + - HOST_GID=${HOST_GID:-1000} volumes: - ./server:/server:rw - ./db:/db:rw @@ -41,6 +47,9 @@ services: build: context: . dockerfile: Dockerfile + args: + - HOST_UID=${HOST_UID:-1000} + - HOST_GID=${HOST_GID:-1000} working_dir: /client volumes: - ./client:/client:rw @@ -73,6 +82,9 @@ services: build: context: . dockerfile: Dockerfile.test + args: + - HOST_UID=${HOST_UID:-1000} + - HOST_GID=${HOST_GID:-1000} working_dir: /opt/puppeteer-test # Mount the workspace so test artifacts (screenshots) are written back to host volumes: