Services
This page covers how to deploy and manage containerized services: Rediaccfiles, service networking, starting/stopping, bulk operations, and autostart.
The Rediaccfile
The Rediaccfile is a Bash script that defines how your services are started and stopped. It is sourced (not executed as a separate process), so its functions share the same shell context and have access to all exported environment variables. It must be named Rediaccfile or rediaccfile (case-insensitive) and placed inside the repository’s mounted filesystem.
Rediaccfiles are discovered in two locations:
- The root of the repository mount path
- First-level subdirectories of the mount path (not recursive)
Hidden directories (names starting with .) are skipped.
Lifecycle Functions
A Rediaccfile contains up to two functions:
| Function | When it runs | Purpose | Error behavior |
|---|---|---|---|
up() | When starting | Start services (e.g., renet compose -- up -d) | Root failure is critical (stops everything). Subdirectory failures are non-critical (logged, continues) |
down() | When stopping | Stop services (e.g., renet compose -- down) | Best-effort — failures are logged but all Rediaccfiles are always attempted |
Both functions are optional. If a function is not defined, it is silently skipped.
Execution Order
- Starting (
up): Root Rediaccfile first, then subdirectories in alphabetical order (A to Z). - Stopping (
down): Subdirectories in reverse alphabetical order (Z to A), then root last.
Environment Variables
When a Rediaccfile function executes, the following environment variables are available:
| Variable | Description | Example |
|---|---|---|
REDIACC_WORKING_DIR | Mount path of the repository | /mnt/rediacc/mounts/abc123 |
REDIACC_REPOSITORY | Repository GUID | a1b2c3d4-e5f6-... |
REDIACC_NETWORK_ID | Network ID (integer) | 2816 |
DOCKER_HOST | Docker socket for this repository’s isolated daemon | unix:///var/run/rediacc/docker-2816.sock |
{SERVICE}_IP | Loopback IP for each service defined in .rediacc.json | POSTGRES_IP=127.0.11.2 |
The {SERVICE}_IP variables are auto-generated from the slot mappings in .rediacc.json and exported before your Rediaccfile functions run. The naming convention converts the service name to uppercase with hyphens replaced by underscores, then appends _IP. For example, a service named listmonk-app with slot 0 becomes LISTMONK_APP_IP=127.0.11.2.
Warning: Do not use
sudo dockerin Rediaccfiles. Thesudocommand resets environment variables, which meansDOCKER_HOSTis lost and Docker commands will target the system daemon instead of the repository’s isolated daemon. This breaks container isolation and can cause port conflicts. Rediacc will block execution if it detectssudo dockerwithout-E.Use
renet composein your Rediaccfiles, it automatically handlesDOCKER_HOST, injects networking labels for route discovery, and configures service networking. See Networking for details on how services are exposed via the reverse proxy. If calling Docker directly, usedockerwithoutsudo, Rediaccfile functions already run with sufficient privileges. If you must use sudo, usesudo -E dockerto preserve environment variables.
renetis the remote low-level tool. For normal user workflows from your workstation, preferrdccommands such asrdc repo upandrdc repo down. See rdc vs renet.
Example
#!/bin/bash
up() {
echo "Starting services..."
renet compose -- up -d
}
down() {
echo "Stopping services..."
renet compose -- down
}
Important: Always use
renet compose --instead ofdocker compose. Therenet composewrapper enforces host networking, IP allocation, and service discovery labels required by renet-proxy. CRIU checkpoint/restore capabilities are added to containers with therediacc.checkpoint=truelabel. Directdocker composeusage is rejected by Rediaccfile validation. See Networking for details.
Multi-Service Layout
For projects with multiple independent service groups, use subdirectories:
/mnt/rediacc/repos/my-app/
├── Rediaccfile # Root: shared setup
├── docker-compose.yml
├── database/
│ ├── Rediaccfile # Database services
│ └── docker-compose.yml
├── backend/
│ ├── Rediaccfile # API server
│ └── docker-compose.yml
└── monitoring/
├── Rediaccfile # Prometheus, Grafana, etc.
└── docker-compose.yml
Execution order for up: root, then backend, database, monitoring (A-Z).
Execution order for down: monitoring, database, backend, then root (Z-A).
Service Networking (.rediacc.json)
Each repository gets a /26 subnet (64 IPs) in the 127.x.x.x loopback range. Services bind to unique loopback IPs so they can run on the same ports without conflicts.
The .rediacc.json File
Maps service names to slot numbers. Each slot corresponds to a unique IP address within the repository’s subnet.
{
"services": {
"api": {"slot": 0},
"postgres": {"slot": 1},
"redis": {"slot": 2}
}
}
Auto-Generation from Docker Compose
You do not need to create .rediacc.json manually. When you run rdc repo up, Rediacc automatically:
- Scans all directories containing a Rediaccfile for compose files (
docker-compose.yml,docker-compose.yaml,compose.yml, orcompose.yaml) - Extracts service names from the
services:section - Assigns the next available slot to new services
- Saves the result to
{repository}/.rediacc.json
IP Calculation
The IP for a service is calculated from the repository’s network ID and the service’s slot. The network ID is split across the second, third, and fourth octets of a 127.x.y.z loopback address. Services start at offset 2:
| Offset | Address | Purpose |
|---|---|---|
| .0 | 127.0.11.0 | Network address (reserved) |
| .1 | 127.0.11.1 | Gateway (reserved) |
| .2. .62 | 127.0.11.2. 127.0.11.62 | Services (slot + 2) |
| .63 | 127.0.11.63 | Broadcast (reserved) |
Example for network ID 2816 (0x0B00), base address 127.0.11.0:
| Service | Slot | IP Address |
|---|---|---|
| api | 0 | 127.0.11.2 |
| postgres | 1 | 127.0.11.3 |
| redis | 2 | 127.0.11.4 |
Each repository supports up to 61 services (slots 0 through 60).
Using Service IPs in Docker Compose
Since each repository runs an isolated Docker daemon, renet compose automatically configures network_mode: host for all services. The kernel transparently rewrites bind() calls to the service’s assigned loopback IP, so services can bind to 0.0.0.0 or localhost without conflicts. For connections to other services, use the service name - renet injects every service name as a hostname that always resolves to the correct IP, even in forks:
services:
postgres:
image: postgres:16
environment:
PGDATA: /var/lib/postgresql/data
POSTGRES_PASSWORD: secret
# No explicit listen_addresses needed - the kernel rewrites bind to the correct loopback IP
api:
image: my-api:latest
environment:
DATABASE_URL: postgresql://postgres:secret@postgres:5432/mydb # use service name
LISTEN_ADDR: 0.0.0.0:8080 # kernel rewrites to service IP
Service names for connections: Use the service name (e.g.
postgres,redis) to connect to other services - renet automatically maps every service name to its loopback IP via/etc/hosts. Embedding${POSTGRES_IP}in connection strings stored inside databases or config files will bake in the raw IP, which breaks fork isolation and is a validation error. The${SERVICE_IP}variables are still available for explicit use, but binding is handled automatically by the kernel.
Note: Do not add
network_mode: hostmanually,renet composeinjects it automatically. Restart policies (e.g.,restart: always) are safe to use, renet auto-strips them for CRIU compatibility and the router watchdog handles container recovery.
Container Recovery & Restart Policy
renet and Docker disagree, on purpose, about how to handle container restarts. Understanding the split is important when debugging why a container did or didn’t come back.
Restart policy translation. When you write restart: always (or unless-stopped, or on-failure) in your compose file, renet strips it when synthesizing the actual compose deployment and replaces it with restart: no. The original value is saved into the repo’s .rediacc.json under services.<name>.restart_policy. This prevents Docker’s daemon-level auto-restart from interfering with CRIU checkpoint/restore (a daemon-driven restart would resume from a stale pre-checkpoint state).
Watchdog enforcement. The router watchdog runs periodically on every machine. Every tick:
- It reads
.rediacc.jsonfor each repo and finds services with a recoverablerestart_policy. - It lists all containers for that repo’s daemon, identifies stopped ones, and restarts them per the saved policy. A 30-second grace period prevents fighting an operator who just ran
docker stop. - The same loop also processes
/var/run/rediacc/cold-backup-<guid>.running.json(see Cold Backup Semantics). Listed containers get restarted regardless of saved policy, because the sidecar means “renet stopped these on purpose and owes the operator a restart.”
Why on-failure can look broken. Docker’s on-failure policy only restarts when the container exits with a non-zero code. A graceful stop (exit 0) from docker stop or a daemon shutdown is not a “failure” and does NOT trigger a restart, neither by Docker’s native logic nor the watchdog’s saved-policy path. The cold-backup sidecar is the safety net: any container we stopped on purpose gets restarted regardless of its policy.
How to interpret the runtime state:
docker inspect <container>→RestartPolicy.Name: will always benofor renet-managed containers. Don’t rely on this for the semantic policy..rediacc.jsonat the repo mount root →services.<name>.restart_policy: the real intent.docker ps --format '{{.Status}}': runtime state.
How to fix a drift. If a container’s .rediacc.json saved policy is wrong (for example, because you edited compose but never recreated the container), re-run rdc repo up --name <repo> -m <machine>. The container is recreated with the updated policy recorded.
Experimental: Cold-backup sidecar-based recovery and the
--sync-certsflag onrdc machine queryshipped in renet 0.9+. Older versions rely purely on savedrestart_policyfor watchdog recovery, which can leaveon-failurecontainers stranded after a cold backup.
Docker bridge networking is disabled for rediacc-managed daemons. Each per-repo daemon is configured with
"bridge": "none"and"iptables": false. A plaindocker run <image>inside a repository shell will still launch, but the container gets only a loopback interface and has no DNS or outbound connectivity. This is by design, since loopback isolation between repos is enforced by eBPF cgroup hooks that a bridged container would bypass. Production services should userenet compose(which injects host networking for you); for ad-hoc debugging, pass--network hostexplicitly:docker run --rm --network host -it ubuntu bash.
Note: Fork repos get auto-routes under the parent’s subdomain:
{service}-fork-{tag}.{repo}.{machine}.{baseDomain}. Custom domains are skipped for forks.
Starting Services
Mount the repository and start all services:
rdc repo up --name my-app -m server-1
| Option | Description |
|---|---|
--skip-router-restart | Skip restarting the route server after the operation |
The execution sequence is:
- Mount the LUKS-encrypted repository (auto-mounts if unmounted)
- Start the isolated Docker daemon
- Auto-generate
.rediacc.jsonfrom compose files - Run
up()in all Rediaccfiles (A-Z order)
After deployment, the output shows a PROXY ROUTES section with the actual URLs for each service. Services with custom Traefik labels (e.g. traefik.http.routers.myapp.rule=Host(...)) show their custom domains as primary URLs:
HTTP services (accessible via proxy after ~3s):
gitlab-server:
HTTPS: https://gitlab.example.com (custom)
Auto: https://gitlab-server.gitlab.server-1.example.com
IP: 127.0.11.130
Services without custom Traefik labels show only the auto-generated route. Use these URLs (not the generic pattern printed by the CLI) for browser access, API calls, and cross-service configuration.
Stopping Services
rdc repo down --name my-app -m server-1
| Option | Description |
|---|---|
--unmount | Unmount the encrypted repository after stopping. If this does not take effect, use rdc repo unmount separately. |
--skip-router-restart | Skip restarting the route server after the operation |
The execution sequence is:
- Run
down()in all Rediaccfiles (Z-A reverse order, best-effort) - Stop the isolated Docker daemon (if
--unmount) - Unmount and close the LUKS-encrypted volume (if
--unmount)
Bulk Operations
Start or stop all repositories on a machine at once:
rdc repo up -m server-1
| Option | Description |
|---|---|
--include-forks | Include forked repositories |
--mount-only | Only mount, don’t start containers |
--dry-run | Show what would be done |
--parallel | Run operations in parallel |
--concurrency <n> | Max concurrent operations (default: 3) |
--skip-router-restart | Skip restarting the route server after the operation |
Autostart on Boot
By default, repositories must be manually mounted and started after a server reboot. Autostart configures repositories to automatically mount, start Docker, and run Rediaccfile up() when the server boots.
How It Works
When you enable autostart for a repository:
- A 256-byte random LUKS keyfile is generated and added to the repository’s LUKS slot 1 (slot 0 remains the user passphrase)
- The keyfile is stored at
{datastore}/.credentials/keys/{guid}.keywith0600permissions (root-only) - A systemd service (
rediacc-autostart) runs at boot to mount all enabled repositories and start their services
On shutdown, the service gracefully stops all services (Rediaccfile down()), stops Docker daemons, and closes LUKS volumes.
Security note: Enabling autostart stores a LUKS keyfile on the server’s disk. Anyone with root access to the server can mount the repository without the passphrase. Evaluate this based on your threat model.
Enable
rdc repo autostart enable --name my-app -m server-1
You will be prompted for the repository passphrase.
Enable All
rdc repo autostart enable -m server-1
Disable
rdc repo autostart disable --name my-app -m server-1
This removes the keyfile and kills LUKS slot 1.
Keyfile Refresh on Deploy
When autostart is enabled, rdc repo up validates the LUKS slot 1 keyfile.
If the on-disk keyfile still matches the LUKS slot, no changes are made.
After transferring a repository between machines via repo push / repo pull,
the keyfile on the new machine won’t match. In this case, repo up automatically
regenerates the keyfile and updates LUKS slot 1. You will see log messages:
Refreshing keyfile credential for <guid>
Killing LUKS slot 1: /mnt/rediacc/repositories/<guid>
Adding keyfile to LUKS slot 1: /mnt/rediacc/repositories/<guid>
This is safe, slot 0 (your passphrase) is never modified. If autostart is not enabled, the check is silently skipped. Failures are non-fatal and do not block the deploy.
List Status
rdc repo autostart list -m server-1
Complete Example
This deploys a web application with PostgreSQL, Redis, and an API server.
1. Set Up
curl -fsSL https://www.rediacc.com/install.sh | bash
rdc config init --name production --ssh-key ~/.ssh/id_ed25519
rdc config machine add --name prod-1 --ip 203.0.113.50 --user deploy
rdc config machine setup --name prod-1
rdc repo create --name webapp -m prod-1 --size 10G
2. Mount and Prepare
rdc repo mount --name webapp -m prod-1
3. Create Application Files
Inside the repository, create:
docker-compose.yml:
services:
postgres:
image: postgres:16
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: webapp
POSTGRES_USER: app
POSTGRES_PASSWORD: changeme
redis:
image: redis:7-alpine
api:
image: myregistry/api:latest
environment:
DATABASE_URL: postgresql://app:changeme@postgres:5432/webapp
REDIS_URL: redis://redis:6379
LISTEN_ADDR: 0.0.0.0:8080
Rediaccfile:
#!/bin/bash
up() {
mkdir -p data/postgres
renet compose -- up -d
echo "Waiting for PostgreSQL..."
for i in $(seq 1 30); do
if renet compose -- exec postgres pg_isready -q 2>/dev/null; then
echo "PostgreSQL is ready."
return 0
fi
sleep 1
done
echo "Warning: PostgreSQL did not become ready within 30 seconds."
}
down() {
renet compose -- down
}
4. Start
rdc repo up --name webapp -m prod-1
5. Enable Autostart
rdc repo autostart enable --name webapp -m prod-1
Using per-repo secrets in compose
The POSTGRES_PASSWORD: changeme placeholder above is fine for a tutorial, but real apps need real credentials, and committing them into the compose file (or a .env file inside the repo) means a fork inherits them too. For deploy-time credentials, use rdc repo secret. Values live outside the encrypted repo image, so forks start with an empty secrets map.
Two delivery modes work in compose:
env mode. Interpolate via ${REDIACC_SECRET_<KEY>} in any environment: value. The renet wrapper passes the value into the container’s environment at deploy time.
file mode. The value lands in a host-side tmpfs file at /var/run/rediacc/secrets/<networkID>/<KEY>, and you mount it into the container via Docker compose’s standard secrets: block. Container reads /run/secrets/<key>. Prefer this mode for anything sensitive. Values never appear in docker inspect or /proc/<pid>/environ.
services:
api:
image: myregistry/api:latest
environment:
DATABASE_URL: ${REDIACC_SECRET_DATABASE_URL}
secrets:
- stripe_live_key
secrets:
stripe_live_key:
file: /var/run/rediacc/secrets/${REDIACC_NETWORK_ID}/STRIPE_LIVE_KEY
Seed the values with rdc repo secret set --name <repo> --key DATABASE_URL --value <val> --mode env --current "" and the file-mode equivalent. See Repositories § Secrets for the full how-to and Per-repo secrets on the cheat sheet for the command reference.
Cross-repo paths are rejected at validate time. A compose
secrets: file:(orconfigs: file:, orenv_file:) that points at another repo’s/var/run/rediacc/secrets/<other-networkID>/directory is hard-rejected by the renet wrapper before docker compose runs.--unsafedoes NOT override. Defense-in-depth: the Landlock sandbox around the Rediaccfile shell scopes reads to the current network’s secrets dir, so acat /var/run/rediacc/secrets/<other>/Xfrom Rediaccfile bash fails with EACCES even if it bypasses the YAML validator. You don’t need to opt in; this is on by default for everyrepo up.