Skip to content

GitHub Action Reference

reoclo/deploy-sync@v2 is the official GitHub Action for syncing proxy routes after an external deploy. It creates a deploy session, discovers services from a Compose file or an explicit list, calls POST /external-deploy/sync, and surfaces the updated FQDNs as step outputs.

reoclo/deploy-sync@v2 is published on the GitHub Marketplace. Pin to @v2 to track the latest v2.x release, or pin to an exact tag for reproducible builds. Source at github.com/reoclo/deploy-sync.

InputRequiredDefaultDescription
api_keyYesReoclo automation API key (rca_). Must have external_deploy in allowed operations.
compose_fileNoPath to a Docker Compose file on the runner (e.g. ./docker-compose.prod.yml). The action reads service names and exposed ports from this file. Mutually exclusive with services.
servicesNoExplicit service list when not using Compose: name:port,name:port (e.g. myapp-web:3000,myapp-api:8080). Mutually exclusive with compose_file.
api_urlNohttps://api.reoclo.comOverride the Reoclo API base URL. Useful for self-hosted or staging environments.
forceNofalseOverride the conflict guard for this run. Use only for legitimate hotfixes — see Deduplication and conflicts.
waitNotrueAfter sync, wait until each app’s proxy route is actually live (managed Caddy running, route applied, container attached to reoclo-proxy) before the step returns. Set false to return as soon as the sync is recorded. See Waiting for convergence.
wait_timeoutNo120Max seconds to wait for convergence when wait is true. The step fails if the routes have not gone live within this window.

One of compose_file or services must be provided. If both are omitted the action fails immediately.

OutputDescription
synced_fqdnsComma-separated list of FQDNs whose routes were updated (e.g. myapp.example.com,api.example.com). Empty if all results were noop.
session_idThe deploy session UUID created for this run. Use this to correlate logs in the Reoclo dashboard.

When compose_file is provided the action parses the file and collects services that meet either of these criteria:

  • The service is connected to a network named reoclo-proxy.
  • The service has the label reoclo.managed=true.

Services that match neither criterion are ignored entirely — they do not appear in unmatched output, they simply are not submitted to the sync API.

For each qualifying service, the action picks the first numeric port listed under ports that maps to a host port. If no ports mapping is present but the service has an expose entry, the first exposed port is used. If no port can be determined, the service is skipped with a warning.

A discovered service is bound to an Application by the reoclo.app (slug) / reoclo.app-id (UUID) label if present, otherwise by container name (linked_container_name, an alias in linked_container_names, or a linked_container_name_pattern glob). Binding by the reoclo.app label is recommended — the container name Reoclo receives is the Compose container_name: or service key, which usually differs from the running container name. See Matching a service to its Application.

Reconciliation is asynchronous: a sync records the desired route and signals the managed-Caddy reconciler, which then attaches the container to reoclo-proxy, writes the Caddyfile, and reloads Caddy a moment later. Without waiting, a step after deploy-sync (a smoke test, a cache warm) can race that and hit a route that isn’t live yet.

By default (wait: true) the action polls GET /external-deploy/status after the sync and only returns once every synced app has converged — managed Caddy running, the applied config reflects the new route, and the container is attached to reoclo-proxy — or until wait_timeout seconds elapse (then the step fails). Set wait: false to return as soon as the sync is recorded.

Against a Reoclo API older than 1.75.0 (no status endpoint) the wait is skipped with a warning rather than failing the deploy.

HTTP status from POST /external-deploy/sessionAction behavior
400 — no identifiers matched an ApplicationThe action fails the step; the API error lists what you submitted and your known Applications. Bind the service with a reoclo.app label, or make the Application’s linked_container_name/alias/pattern match the service name. See Troubleshooting.
403 — API key lacks external_deploy scopeThe action fails with ::error::API key does not have external_deploy permission. Add external_deploy to the key’s allowed operations in the dashboard.
401 — invalid or revoked keyThe action fails immediately. Verify the secret value and that the key has not been revoked.

The action passes each deployment triple — container_name, container_port, image_tag — to the sync API. The API computes a SHA-256 signature over these fields:

  • If the signature matches the last active sync for that Application, the result is noop and the proxy is not touched.
  • If the signature differs but a sync for the same Application occurred within the past 30 seconds, the result is conflict. The CLI exits non-zero, so the action step fails. Either wait ~30 seconds and rerun, or set the force: true input on the step to override the guard for that run (use only for legitimate hotfixes). force is a first-class action input as of @v2 — you no longer need to call the API directly.

Sessions expire automatically after approximately 15 minutes. The action creates a fresh session at the start of each run, so session expiry is only relevant if your workflow takes more than 15 minutes between the session-creation step and the sync step. In that case, rerun the failed job — the action will create a new session on retry.

name: Deploy and sync
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy containers on server
uses: reoclo/run@v2
with:
api_key: ${{ secrets.REOCLO_API_KEY }}
server_id: ${{ secrets.REOCLO_SERVER_ID }}
command: |
cd /srv/reoclo/myapp
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
timeout: 300
- name: Sync Reoclo proxy routes
id: sync
uses: reoclo/deploy-sync@v2
with:
api_key: ${{ secrets.REOCLO_API_KEY }}
compose_file: ./docker-compose.prod.yml
- name: Print synced FQDNs
run: echo "Live at ${{ steps.sync.outputs.synced_fqdns }}"

reoclo/run accepts an optional env input for forwarding environment variables into the remote command. GitHub Actions inputs are always strings, so env is parsed as a KEY=VALUE block — not as a YAML map.

Correct — block string with one KEY=VALUE per line:

- name: Deploy with env vars
uses: reoclo/run@v2
with:
api_key: ${{ secrets.REOCLO_API_KEY }}
server_id: ${{ secrets.REOCLO_SERVER_ID }}
command: |
cd /srv/reoclo/myapp
docker compose -f docker-compose.prod.yml up -d
env: |
PROD_ENV_B64=${{ secrets.PROD_ENV_B64 }}
LOG_LEVEL=info

Incorrect — YAML map (rejected at parse time):

# DO NOT DO THIS — workflow file will not parse
- uses: reoclo/run@v2
with:
env:
PROD_ENV_B64: ${{ secrets.PROD_ENV_B64 }}
LOG_LEVEL: info