Skip to content

Recipe: Diagnose a deployment failure

Use this recipe when:

  • The reoclo/deploy-sync@v2 step failed with a non-zero exit code.
  • The sync step succeeded but status is conflict or noop when synced was expected.
  • The API key verification step returned an unexpected status.
  • The site returns 502 after a synced result.

403 Forbidden on POST /external-deploy/session

Section titled “403 Forbidden on POST /external-deploy/session”

Root cause: The rca_ key does not have external_deploy in its allowed_operations.

Fix:

  1. Go to https://app.reoclo.comAPI Keys > Automation Keys.
  2. Find the key used in the workflow (match by prefix shown in the table).
  3. Add external_deploy to Allowed Operations.
  4. No key rotation needed — the change takes effect immediately.

Verify fix: there is no public key-introspection endpoint — probe the session endpoint instead and read the HTTP status.

Terminal window
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://api.reoclo.com/external-deploy/session \
-H "Authorization: Bearer $REOCLO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"container_names": ["YOUR_CONTAINER_NAME"]}'

Expect 201. A 403 means external_deploy is still missing from the key’s allowed operations.


Root cause: The token is invalid, expired, or revoked.

  • If the token prefix is rca_: the automation key was revoked or the secret value is wrong.
  • If the token prefix is rds_: the session expired (15-minute limit) or was explicitly revoked.

Fix for rca_ 401:

  1. In the GitHub repository, go to Settings > Secrets and variables > Actions.
  2. Verify REOCLO_API_KEY contains a valid rca_ key.
  3. If the key was revoked, create a new one in the dashboard and update the secret.

Fix for rds_ 401 (session expired): Rerun the failed GitHub Actions job. The action creates a fresh session at the start of each run. If the workflow consistently takes more than 15 minutes before reaching the sync step, move the sync step earlier in the job.


400 Bad Request — “no container names matched any Application”

Section titled “400 Bad Request — “no container names matched any Application””

Root cause: None of the container names submitted to POST /external-deploy/session match the linked_container_name of any Application in the tenant.

Diagnosis: there is no public endpoint that lists Applications for an automation key. The session endpoint instead reports which submitted names matched an Application (applications) and which did not (unmatched). Submit the container names you deploy:

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

Any name returned under unmatched does not match an Application’s linked_container_name. Compare against the container names in your Compose file or services action input.

Fix:

  • If the Application’s linked_container_name is wrong: update it in the dashboard under the Application’s settings.
  • If the Compose service name is wrong: correct the service name or services input in the workflow.

Note: Docker Compose may prefix the project name to container names (e.g. myapp-web becomes myapp-myapp-web-1). Verify the actual running container name with docker ps --format '{{.Names}}' on the server.


409 Conflict / status: conflict in results

Section titled “409 Conflict / status: conflict in results”

Root cause: A sync for the same Application occurred within the past 30 seconds with a different signature.

Common causes:

  • Two workflow runs triggered in rapid succession.
  • A rerun of a failed job while a parallel run is still in progress.

Fix — wait and retry: Wait 30 seconds and rerun the job. The conflict window will have passed.

Fix — force override: set the force: true input on the reoclo/deploy-sync@v2 step to override the conflict guard for that run:

- name: Sync Reoclo proxy routes
uses: reoclo/deploy-sync@v2
with:
api_key: ${{ secrets.REOCLO_API_KEY }}
compose_file: ./docker-compose.prod.yml
force: true

Use force only when you are certain the new configuration is correct and the conflict cannot be resolved by waiting. If you drive the API directly instead of the action, pass force: true per deployment entry to POST /external-deploy/sync:

Terminal window
curl -X POST https://api.reoclo.com/external-deploy/sync \
-H "Authorization: Bearer $RDS_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deployments": [
{
"container_name": "myapp-web",
"container_port": 3000,
"image_tag": "ghcr.io/myorg/myapp:abc123",
"force": true
}
]
}'

Root cause: The signature for this deployment (container name + port + image tag) is identical to the last active sync. Reoclo deduplicates identical configurations.

Diagnosis:

  • If you always deploy with image_tag: latest, every run produces the same tag string even if the underlying image changed. Reoclo cannot distinguish them.

Fix: Use a specific image tag in your workflow — commit SHA, semantic version, or timestamp:

image: ghcr.io/myorg/myapp:${{ github.sha }}

Pass the tag as a build argument or environment variable and substitute it into your Compose file before docker compose up.


Per-item error: “container_name not authorized for this session”

Section titled “Per-item error: “container_name not authorized for this session””

Root cause: The container_name in the POST /external-deploy/sync body was not part of the session’s authorized container list. This means it was either not included in the container_names array when the session was created, or it did not match any Application at session creation time.

Fix: Ensure the container name passed to /sync exactly matches the value in the Application’s linked_container_name field and that it was submitted to the session endpoint. When using the action, this is handled automatically from the Compose file — the mismatch means the service is not on reoclo-proxy and was not submitted, or the service name differs from the Application’s linked name.


Root cause: The proxy route was updated but Caddy cannot reach the container. Caddy resolves container_name inside the reoclo-proxy Docker network. The container is not on that network.

Diagnosis steps on the server:

Terminal window
# Confirm container is on reoclo-proxy
docker network inspect reoclo-proxy --format '{{range .Containers}}{{.Name}} {{end}}'
# Confirm container is listening on the expected port
docker inspect myapp-web --format '{{json .NetworkSettings.Ports}}'

Fix: Add reoclo-proxy as an external network in the Compose file and attach the service to it:

services:
myapp-web:
networks:
- reoclo-proxy
networks:
reoclo-proxy:
external: true

See Compose Deployments for the full network configuration guide.