Deployment Folder & Permissions
External Deployments need a folder on the server where your docker-compose.yml, .env files, and any data volumes live. The Reoclo runner — invoked from CI by reoclo/run@v2 — has to read and write that folder. So do you, when you scp a new Compose file or edit one over SSH.
Without any setup, those two are different Unix users with different primary groups, which is why every external deploy you’ve ever done has involved a chown somewhere. This page explains the layout Reoclo provisions during install to make that friction go away.
Recommended layout: /srv/reoclo/<app-name>/
Section titled “Recommended layout: /srv/reoclo/<app-name>/”One folder per app, all under /srv/reoclo/:
/srv/reoclo/├── portfolio-web/│ ├── docker-compose.prod.yml│ └── .env├── api/│ └── docker-compose.prod.yml└── jobs/ └── docker-compose.prod.ymlWhy /srv/reoclo/ and not /opt/...: /opt/reoclo/ is reserved for the runner binary itself. Keeping app data in /srv/ (the FHS-conventional spot for “data served by this host”) means a cleanup of one will never accidentally take out the other.
The reoclo-deploy group
Section titled “The reoclo-deploy group”When you install the runner with curl -sSL https://get.reoclo.com | bash, the installer does the following on Linux (rootful Docker mode):
groupadd -f reoclo-deployusermod -aG reoclo-deploy reoclo # the runner usermkdir -p /srv/reoclochown root:reoclo-deploy /srv/reoclochmod 2775 /srv/reoclo # 2 = setgidThe setgid bit (2 in 2775) is the load-bearing part. With it set on the parent, every file and subdirectory created inside /srv/reoclo/ automatically inherits reoclo-deploy as its group — regardless of whether the runner, a CI workflow, or a human admin created it. That means no chown after every edit.
One-time setup: join the group as a human admin
Section titled “One-time setup: join the group as a human admin”The runner is already in reoclo-deploy. Each human who needs to drop files in /srv/reoclo/<app>/ (over SSH, scp, or a config-management tool) joins the group once:
-
Add your user to the group:
Terminal window sudo usermod -aG reoclo-deploy "$USER" -
Activate the new membership in the current shell:
Terminal window newgrp reoclo-deployOr log out and back in — group membership only takes effect on new login shells.
-
Verify:
Terminal window id -nG | tr ' ' '\n' | grep -x reoclo-deployThe command should print
reoclo-deploy.
Per-app workflow
Section titled “Per-app workflow”Once you are in the group, creating a folder for a new app needs no special commands:
mkdir /srv/reoclo/portfolio-webcd /srv/reoclo/portfolio-web
# Drop in your docker-compose.yml, .env, etc.$EDITOR docker-compose.prod.yml
# Verify the runner can write here — should print "reoclo-deploy" and a "g+s" mode.stat -c '%U:%G %A' .# expected: root:reoclo-deploy drwxrwsr-x (or similar; the trailing "s" is setgid)Your CI workflow then targets the same path from reoclo/run@v2:
- name: Deploy containers uses: reoclo/run@v2 with: api_key: ${{ secrets.REOCLO_API_KEY }} server_id: ${{ secrets.REOCLO_SERVER_ID }} command: | cd /srv/reoclo/portfolio-web docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d timeout: 300The runner can cd into the folder, read the Compose file you authored, write any updates Docker emits (e.g. .docker-compose.lock), and the next time you edit the file over SSH neither of you needs to fix permissions.
Rootless Docker
Section titled “Rootless Docker”If your server runs rootless Docker, the runner executes as the same user that owns the Docker socket — typically you, the operator. Because the runner and the human admin are the same Unix user, there is no permission gap between them and no shared group is needed.
The installer still provisions /srv/reoclo so you have a canonical, sudo-free place to put app folders. The only difference from rootful is the ownership:
# what the installer does on a rootless hostsudo mkdir -p /srv/reoclosudo chown <rootless-user>:<rootless-group> /srv/reoclosudo chmod 0755 /srv/reocloPer-app workflow needs no sudo and no chown:
mkdir /srv/reoclo/portfolio-webcd /srv/reoclo/portfolio-web$EDITOR docker-compose.prod.ymlThis is the same path layout as the rootful case, just without the group plumbing. CI workflows can cd /srv/reoclo/<app-name> in both modes — the docs, the agent recipes, and the reoclo/run@v2 examples all assume /srv/reoclo/<app-name>/ regardless of which Docker mode the host runs in.
What about existing folders?
Section titled “What about existing folders?”The setgid inheritance only applies to files and folders created after the bit is set on the parent. If you already had /srv/myapp (or /opt/myapp) before installing the runner, retrofit it once:
sudo chgrp -R reoclo-deploy /srv/myappsudo find /srv/myapp -type d -exec chmod g+s {} \;sudo find /srv/myapp -type f -exec chmod g+rw {} \;Future edits stay group-correct without re-running these commands.
Minimal-permissions alternative
Section titled “Minimal-permissions alternative”If no human ever touches the folder — every change is written through the runner via CI — you don’t strictly need the shared group. The runner can own the folder directly:
sudo mkdir -p /srv/reoclo/myappsudo chown reoclo:reoclo /srv/reoclo/myappsudo chmod 0750 /srv/reoclo/myappIn practice almost every workflow eventually grows a “let me just ssh in and tweak this one line” moment, which is exactly when the shared-group setup pays off. Use this only when you are sure you never want manual writes.
Troubleshooting
Section titled “Troubleshooting”Permission denied running mkdir /srv/reoclo/<app>
Section titled “Permission denied running mkdir /srv/reoclo/<app>”/srv/reoclo does not exist on this server yet, so mkdir is trying (and failing) to create it as a child of root-owned /srv. /srv is root:root 0755 by FHS convention, so unprivileged users can’t write there directly — that’s intentional, and exactly what the installer is for. The installer creates /srv/reoclo once with the right ownership; from then on, per-app mkdir under it needs no sudo.
If you installed the runner before v1.24.0, re-run the installer once on the affected host:
curl -sSL https://get.reoclo.com/install.sh | bash -s -- --token <NEW_TOKEN>Then mkdir /srv/reoclo/<app> works without sudo. The installer is idempotent and safe to re-run.
If you can’t re-run the installer right now (no fresh registration token, locked-down host), you can provision /srv/reoclo manually with the same shape the installer would:
# rootful Docker modesudo groupadd -f reoclo-deploysudo usermod -aG reoclo-deploy reoclosudo mkdir -p /srv/reoclosudo chown root:reoclo-deploy /srv/reoclosudo chmod 2775 /srv/reoclo
# rootless Docker mode (runner = your user)sudo mkdir -p /srv/reoclosudo chown "$USER":"$(id -gn)" /srv/reoclosudo chmod 0755 /srv/reocloPermission denied writing to /srv/reoclo/myapp/.env
Section titled “Permission denied writing to /srv/reoclo/myapp/.env”You are not in the reoclo-deploy group in your current shell. Run id to confirm. If it is missing from the list, run newgrp reoclo-deploy (active shell only) or log out and back in (all future shells).
If id shows reoclo-deploy but writes still fail, check the file mode — files you created before joining the group may still be 0644 (group-readable, not group-writable). Run chmod g+w <file> to fix individual files, or find /srv/reoclo/myapp -type f -exec chmod g+rw {} \; to fix a whole tree.
Runner can read but not write a file I created
Section titled “Runner can read but not write a file I created”Your umask is 022 (the common default), which creates files as 0644. The runner is in the group but the group does not have write permission on that specific file. Either set umask 002 in your shell rc (recommended) or run chmod g+w <file> after creating it. Files the runner itself creates are already group-writable — its systemd unit sets UMask=0002.
groupadd: command not found on minimal images
Section titled “groupadd: command not found on minimal images”The installer needs groupadd and usermod from the shadow-utils package. This is preinstalled on every common distro (Ubuntu, Debian, Alpine, RHEL, Rocky, openSUSE). If you are on a stripped-down container image and the installer fails here, install shadow-utils (RHEL family) or shadow (Alpine) and re-run.
Next steps
Section titled “Next steps”With the folder and permissions in place, wire up your deploy pipeline. Every guide below targets /srv/reoclo/<app-name>/:
- GitHub Actions Integration — deploy with
reoclo/runandreoclo/checkout. - External Deployments → Quickstart — add proxy-route sync with
reoclo/deploy-sync. - CLI CI/CD automation — deploy from any CI runner with the
reocloCLI.