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.
Inputs
Section titled “Inputs”| Input | Required | Default | Description |
|---|---|---|---|
api_key | Yes | — | Reoclo automation API key (rca_). Must have external_deploy in allowed operations. |
compose_file | No | — | Path 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. |
services | No | — | Explicit service list when not using Compose: name:port,name:port (e.g. myapp-web:3000,myapp-api:8080). Mutually exclusive with compose_file. |
api_url | No | https://api.reoclo.com | Override the Reoclo API base URL. Useful for self-hosted or staging environments. |
force | No | false | Override the conflict guard for this run. Use only for legitimate hotfixes — see Deduplication and conflicts. |
wait | No | true | After 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_timeout | No | 120 | Max 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.
Outputs
Section titled “Outputs”| Output | Description |
|---|---|
synced_fqdns | Comma-separated list of FQDNs whose routes were updated (e.g. myapp.example.com,api.example.com). Empty if all results were noop. |
session_id | The deploy session UUID created for this run. Use this to correlate logs in the Reoclo dashboard. |
Behavior notes
Section titled “Behavior notes”Compose service discovery
Section titled “Compose service discovery”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.
Waiting for convergence
Section titled “Waiting for convergence”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.
Session creation errors
Section titled “Session creation errors”HTTP status from POST /external-deploy/session | Action behavior |
|---|---|
400 — no identifiers matched an Application | The 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 scope | The 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 key | The action fails immediately. Verify the secret value and that the key has not been revoked. |
Deduplication and conflicts
Section titled “Deduplication and conflicts”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
noopand 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 theforce: trueinput on the step to override the guard for that run (use only for legitimate hotfixes).forceis a first-class action input as of@v2— you no longer need to call the API directly.
Session expiry
Section titled “Session expiry”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.
Full example
Section titled “Full example”name: Deploy and syncon: 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@v2 — env input format
Section titled “reoclo/run@v2 — env input format”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=infoIncorrect — 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