Skip to content

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.

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.yml

Why /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.

When you install the runner with curl -sSL https://get.reoclo.com | bash, the installer does the following on Linux (rootful Docker mode):

Terminal window
groupadd -f reoclo-deploy
usermod -aG reoclo-deploy reoclo # the runner user
mkdir -p /srv/reoclo
chown root:reoclo-deploy /srv/reoclo
chmod 2775 /srv/reoclo # 2 = setgid

The 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:

  1. Add your user to the group:

    Terminal window
    sudo usermod -aG reoclo-deploy "$USER"
  2. Activate the new membership in the current shell:

    Terminal window
    newgrp reoclo-deploy

    Or log out and back in — group membership only takes effect on new login shells.

  3. Verify:

    Terminal window
    id -nG | tr ' ' '\n' | grep -x reoclo-deploy

    The command should print reoclo-deploy.

Once you are in the group, creating a folder for a new app needs no special commands:

Terminal window
mkdir /srv/reoclo/portfolio-web
cd /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: 300

The 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.

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:

Terminal window
# what the installer does on a rootless host
sudo mkdir -p /srv/reoclo
sudo chown <rootless-user>:<rootless-group> /srv/reoclo
sudo chmod 0755 /srv/reoclo

Per-app workflow needs no sudo and no chown:

Terminal window
mkdir /srv/reoclo/portfolio-web
cd /srv/reoclo/portfolio-web
$EDITOR docker-compose.prod.yml

This 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.

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:

Terminal window
sudo chgrp -R reoclo-deploy /srv/myapp
sudo 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.

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:

Terminal window
sudo mkdir -p /srv/reoclo/myapp
sudo chown reoclo:reoclo /srv/reoclo/myapp
sudo chmod 0750 /srv/reoclo/myapp

In 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.

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:

Terminal window
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:

Terminal window
# rootful Docker mode
sudo groupadd -f reoclo-deploy
sudo usermod -aG reoclo-deploy reoclo
sudo mkdir -p /srv/reoclo
sudo chown root:reoclo-deploy /srv/reoclo
sudo chmod 2775 /srv/reoclo
# rootless Docker mode (runner = your user)
sudo mkdir -p /srv/reoclo
sudo chown "$USER":"$(id -gn)" /srv/reoclo
sudo chmod 0755 /srv/reoclo

Permission 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.

With the folder and permissions in place, wire up your deploy pipeline. Every guide below targets /srv/reoclo/<app-name>/: