Skip to content

Recipe: Enhance an existing GitHub workflow with Reoclo route sync

Use this recipe when:

  • A .github/workflows/*.yml file exists in the repository.
  • At least one workflow step contains docker compose, docker run, or docker stack.
  • The user has a Reoclo account and at least one Application with a linked_container_name configured.

Do not use this recipe if the repository has no existing Docker workflow. Use Set Up External Deployment instead.

Before making any changes, confirm the recipe applies.

Step 1. Find workflow files with Docker commands:

Terminal window
grep -rl "docker" .github/workflows/

For each matched file, check for docker compose up, docker compose up -d, docker run, or docker stack deploy.

Step 2. Confirm a Reoclo Application matches the container (and the key has the operation):

There is no public endpoint that lists Applications, and no key-introspection endpoint, for an automation key. Probe the deploy-session endpoint instead. It returns the Applications whose linked_container_name matched (applications) and the names that did not (unmatched), and it returns 403 when the key lacks the deploy_session:open operation. Read both the body and the HTTP status:

Terminal window
curl -s -w '\n%{http_code}\n' -X POST https://api.reoclo.com/external-deploy/session \
-H "Authorization: Bearer $REOCLO_AUTOMATION_KEY" \
-H "Content-Type: application/json" \
-d '{"container_names": ["YOUR_CONTAINER_NAME"]}'
  • A trailing 201 with your container under applications → the recipe can proceed.
  • Your container under unmatched (or an empty applications) → no Application has a matching linked_container_name. Stop and tell the user a Reoclo Application linked to this container must exist first.
  • A trailing 403 → continue to Step 3.

Step 3. If Step 2 returned 403, add the deploy_session:open operation.

The 403 from the Step 2 probe is the signal that the rca_ key is missing deploy_session:open. Instruct the user to add it to the key’s allowed operations in the dashboard at https://app.reoclo.com under API Keys > Automation Keys, then re-run Step 2. A 201 confirms the operation is present.

Insert a reoclo/deploy-sync@v2 step immediately after the docker compose up / docker run step in each qualifying workflow job.

Before:

- name: Deploy containers
uses: reoclo/run@v2
with:
api_key: ${{ secrets.REOCLO_AUTOMATION_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

After:

- name: Deploy containers
uses: reoclo/run@v2
with:
api_key: ${{ secrets.REOCLO_AUTOMATION_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
uses: reoclo/deploy-sync@v2
with:
api_key: ${{ secrets.REOCLO_AUTOMATION_KEY }}
compose_file: ./docker-compose.prod.yml

Rules for placing the step:

  • Always insert after the last docker compose up / docker run step in the job, not before it.
  • If the workflow uses a matrix strategy, add the step inside the matrix job so it runs once per matrix entry.
  • If the job has a needs: dependency on a build job, place the sync step in the deploy job, not the build job.
  • Do not add the step to jobs that only build images without deploying them.

If the workflow does not use a Compose file (bare docker run commands), use the services input instead:

- name: Sync Reoclo proxy routes
uses: reoclo/deploy-sync@v2
with:
api_key: ${{ secrets.REOCLO_AUTOMATION_KEY }}
services: "myapp-web:3000"

Determine the correct name:port value by reading the container name and port from the docker run command in the workflow.

After editing the workflow file, run these checks without committing.

Check 1. YAML is valid:

Terminal window
python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" .github/workflows/deploy.yml
echo "YAML OK"

Check 2. Confirm the API key can create a session (dry run):

Terminal window
curl -s -X POST https://api.reoclo.com/external-deploy/session \
-H "Authorization: Bearer $REOCLO_AUTOMATION_KEY" \
-H "Content-Type: application/json" \
-d '{"container_names": ["YOUR_CONTAINER_NAME"]}' \
| jq '{session_id, applications: [.applications[].linked_container_name], unmatched}'

Replace YOUR_CONTAINER_NAME with the container name from your Compose file or docker run command.

Expected shape:

{
"session_id": "uuid",
"applications": ["your-container-name"],
"unmatched": []
}

If applications is empty and unmatched contains your container name, the Application’s linked_container_name does not match. Inform the user and stop.

Check 3. Clean up the test session:

Terminal window
SESSION_ID=$(curl -s -X POST https://api.reoclo.com/external-deploy/session \
-H "Authorization: Bearer $REOCLO_AUTOMATION_KEY" \
-H "Content-Type: application/json" \
-d '{"container_names": ["YOUR_CONTAINER_NAME"]}' \
| jq -r '.session_id')
curl -s -X DELETE "https://api.reoclo.com/external-deploy/session/$SESSION_ID" \
-H "Authorization: Bearer $REOCLO_AUTOMATION_KEY" \
-o /dev/null -w "%{http_code}"

Expected: 204

  • Do not add application_id as an action input. The action does not accept an application_id. Apps are resolved by matching the service name in the Compose file to the Application’s linked_container_name on the server. There is no application ID input.
  • Do not pass server_id to reoclo/deploy-sync@v2. The action does not accept server_id. Server association is resolved via the session, which is scoped to the tenant of the rca_ key.
  • Do not rename the step to avoid conflicts. The step name is cosmetic. It does not affect behavior.
  • force: true is a valid action input as of @v2. Add it to the reoclo/deploy-sync@v2 step to override the 30-second conflict guard for that run. Use it only for legitimate hotfixes, since a conflict result otherwise fails the step. (Passing force per-deployment to POST /external-deploy/sync directly also works.)
  • Token prefix matters. The api_key input must be an rca_ key (Automation Key). Tenant Integration Keys (rk_t_*) have been retired and are rejected by the API.

Auth: Authorization: Bearer rca_...

Request:

{
"container_names": ["string"],
"workflow_run_id": "string (optional)",
"commit_sha": "string (optional)"
}

Response 201:

{
"session_id": "uuid",
"session_token": "rds_...",
"expires_at": "2026-01-01T00:15:00Z",
"applications": [
{
"id": "uuid",
"linked_container_name": "string",
"container_port": 3000,
"bound_fqdns": ["example.com"]
}
],
"unmatched": ["container_name_with_no_app"]
}

Errors: 403 key lacks scope, 400 no container names matched.

Auth: Authorization: Bearer rds_...

Request:

{
"deployments": [
{
"container_name": "string",
"container_port": 3000,
"image_tag": "ghcr.io/org/repo:sha",
"force": false
}
]
}

Response 200:

{
"session_id": "uuid",
"results": [
{
"application_id": "uuid",
"container_name": "string",
"status": "synced | noop | conflict | drift_recovered",
"signature_hash": "sha256...",
"synced_fqdns": ["example.com"],
"reason": "string (present for conflict)"
}
],
"errors": [
{
"container_name": "string",
"reason": "string"
}
]
}

DELETE /external-deploy/session/{session_id}

Section titled “DELETE /external-deploy/session/{session_id}”

Auth: Bearer rca_... (matching tenant) OR Bearer rds_... (the session itself).

Response: 204 No Content