# Deployment Guide > Ready for your project to see the light? You may be eligible for deployment on > **rethink-net** — our fleet of Ubuntu 26.x servers, ready to host whatever > you've built. !!! tip "Eligible" APIs, websites, applets, bots, monitors. The whole network runs on a few simple, consistent rules — get your container to follow them and deploying is mostly handing us a `compose.yaml`. ## Docker — the services account Every service runs containerized as the shared **`services`** account: **uid/gid 1337**, fixed fleet-wide. Build your image to be **uid-agnostic** so it runs cleanly as that account. ```dockerfile FROM python:3.12-slim ENV HOME=/tmp # (1)! WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends git \ && rm -rf /var/lib/apt/lists/* # (2)! COPY . . RUN pip install --no-cache-dir . \ && chmod -R a+rwX /app # (3)! CMD ["python", "-m", "yourapp"] ``` 1. `HOME=/tmp` — the `services` account has no home dir; anything writing to `$HOME` (caches, configs) needs a writable target. 2. Install `git` **only if** you `pip install` from git, then clean the apt lists to keep the image small. 3. `chmod -R a+rwX /app` makes the app tree writable by **any** uid — this is what "uid-agnostic" means. Note it only covers **non-mounted** dirs (see the [footgun](#permissions-the-bind-mount-footgun)). ```yaml services: yourapp: build: . user: "1337:1337" # (1)! environment: HOME: /tmp volumes: - /srv/configs/:/app/config:ro # (2)! - /srv//:/app/logs # (3)! - yourapp-data:/app/data # (4)! volumes: yourapp-data: ``` 1. Run as the shared account. **No** in-container `user`/`useradd` — don't bake a user into the image; set it here. 2. Configs: host-managed bind mount, mounted **read-only**. 3. Logs: bind mount — live and rolled, scraped for monitoring. 4. Everything else: a **named volume**. Docker owns it, so there are no host permissions to fiddle with. ## Paths and mounts | What | Where | How | | --- | --- | --- | | Configs | `/srv/configs//` | bind mount, host-managed | | Logs | `/srv///` | bind mount; live + rolled, scraped | | Caches, profiles, scratch | named volume | Docker manages ownership | ## Permissions — the bind-mount footgun !!! danger "Bind-mount sources must exist and be 1337-owned *before* `up`" `docker compose up` does **not** create bind-mount directories as you. If a bind-mount source is missing, the Docker daemon (**root**) creates it **as root** — and your container (**1337**) then can't write it. Logs silently fall back to console-only; caches re-download every run. So: - **Bind-mount sources (configs, logs) must EXIST and be 1337-owned before `up`** — handled at provisioning, not a per-deploy chown hook. - **Named volumes avoid this entirely** — use them for anything that doesn't need host visibility. !!! note "Why `chmod a+rwX /app` doesn't save you here" The Dockerfile `chmod` only covers **non-mounted** dirs. A bind mount overrides the image directory with the host directory, so for mounted paths the **host-side ownership wins** — the image's permissions are irrelevant. ## What your compose / Dockerfile needs - `user: "1337:1337"` - bind mounts for **configs + logs** - named volumes for **the rest** - secrets bind-mounted **`:ro`** - `HOME=/tmp` - `chmod -R a+rwX /app` - `git` in the build if you `pip install` from git ## Secrets !!! warning "Secrets never go in the image" We do **not** commit secrets (usually, lol). They stay **gitignored**, live on the host at `/srv/configs//`, and are bind-mounted **read-only** at runtime. Add them to `.dockerignore` so a `COPY . .` can't sweep them into a layer. Rotating a secret = edit the host file and restart. No rebuild. ```bash vim /srv/configs//secrets.env # edit on the host docker compose restart yourapp # pick up the change — no rebuild ```