Skip to content

ci(pesacheck_meedan_bridge): Add continuous deployment workflow - #1209

Merged
koechkevin merged 10 commits into
mainfrom
chore/pesacheck-bridge-cd
Sep 24, 2026
Merged

koechkevin merged 10 commits into
mainfrom
chore/pesacheck-bridge-cd

Conversation

@koechkevin

@koechkevin koechkevin commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Why

The bridge had no deploy workflow: .github/workflows/ only covered TwoopsTracker, so pesacheck_meedan_bridge was built and deployed by hand.

What changed

.github/workflows/pesacheck-meedan-bridge-cd.yaml

Modelled on twoops-tracker-cd.yaml, pointed at the bridge:

  • Trigger: push to main touching pesacheck_meedan_bridge/py/VERSION, so a deploy needs a deliberate version bump.
  • Steps: set VERSION from py/VERSION → init/bootstrap Pants → tailor --check → pants lint --lint-skip-formatters pesacheck_meedan_bridge/:: → pants package pesacheck_meedan_bridge/docker/:: → push codeforafrica/pesacheck_meedan_bridge:$VERSION to DockerHub → deploy to Dokku.
  • Dokku: ssh://dokku@dokku-1.dev.codeforafrica.org/pesacheck-meedan-bridge, deployed with deploy_docker_image, i.e. dokku git:from-image against the tag we just pushed.
  • Reuses the existing secrets: DOCKER_HUB_USERNAME, DOCKER_HUB_ACCESS_TOKEN, SSH_PRIVATE_KEY.

main.py: identify the bridge to Ghost

Requests went out as python-requests/x.y.z, which Cloudflare challenges in front of pesacheck.org: the first production run got the "Just a moment..." interstitial instead of the Content API, and the error surfaced in Sentry. The bridge now sends:

User-Agent: pesacheck-meedan-bridge (+https://github.com/CodeForAfrica/api)

That alone was not enough from the Dokku host, where the challenge is driven by IP reputation. What fixed it is a Cloudflare WAF skip rule on the pesacheck.org zone for /ghost/api/content/* from the host's IP (3.248.206.242), skipping Super Bot Fight Mode, managed rules, rate limiting and remaining custom rules. Worth knowing that the rule is pinned to an ordinary EC2 public IP: if the instance is ever stopped and started, the IP changes and this failure returns. An Elastic IP, or matching on a secret header instead, would make it sturdier.

Version

VERSION → 0.1.21. 0.1.20 is already built and deployed (from testing this workflow), so merging at 0.1.20 would fire the workflow and fail on git:from-image's "No changes detected". 0.1.21 gives a clean first deploy from main.

Testing

Run from this branch, with a temporary branch trigger that has been removed again. Five runs:

  1. ❌ exit 127 on ./pants → fixed by calling pants.
  2. ❌ Dokku buildpack build failed → fixed with deploy_docker_image.
  3. ✅ Full run: built, pushed 0.1.19, git:from-image, "Application deployed".
  4. ✅ Full run for 0.1.20, shipping the User-Agent change.
  5. ❌ Expected: re-deploying an image tag already live exits non-zero with "No changes detected, skipping git commit". Worth knowing, though the VERSION trigger means each real deploy carries a new tag.

Also verified on the host: cron:list shows the @daily ./pex entry from app.json inside the image, and cron:run executes it. Two Dokku gotchas found while testing:

  • Don't run ps:rebuild on this app. There's nothing to rebuild for an image deploy, and it resurrects whatever Dockerfile the app's git HEAD points at. It rolled prod back to 0.1.16 and, because that image predates app.json, deregistered the cron.
  • dokku run refuses with Invalid image stage detected: expected 'release', got '', since images built outside Dokku carry no com.dokku.image-stage label. cron:run works, and dokku enter <app> web is the fallback for ad-hoc commands.

yamllint passes with the repo's .yamllint config.

Comment thread .github/workflows/pesacheck-meedan-bridge-cd.yaml Fixed
Mirror the TwoopsTracker CD workflow for the bridge: on a push to main
that changes pesacheck_meedan_bridge/py/VERSION, lint, build the Docker
image with Pants, push it to DockerHub and deploy to Dokku.
@koechkevin
koechkevin force-pushed the chore/pesacheck-bridge-cd branch from 11519b7 to 964781a Compare September 24, 2026 09:27
Temporarily runs the workflow on pushes to chore/pesacheck-bridge-cd and
bumps VERSION to 0.1.19 so the paths filter matches. The branch entry is
removed before merging.
The repo has no ./pants launcher script, so the tailor and package steps
failed with exit 127. twoops-tracker-cd.yaml has the same two calls and
would fail the same way.

Also temporarily triggers on changes to the workflow file so it can be
tested without bumping VERSION each time.
Without deploy_docker_image, Dokku tries to build the pushed monorepo
with the Heroku Python buildpack and fails: there is no top-level
requirements.txt. Deploy the image pushed to DockerHub instead.
Requests went out as python-requests/x.y.z, which Cloudflare challenges in
front of pesacheck.org: the daily run got the "Just a moment..." page
instead of the Content API. Name the bridge instead.

A custom User-Agent may not be enough on its own where the challenge is
driven by IP reputation; the Content API path still wants a Cloudflare
allowlist for the Dokku host.
Ships the identifying User-Agent for the Ghost Content API.
With deploy_docker_image set, dokku/github-action runs `git:from-image`
and never pushes a branch, so branch/git_push_flags were dead inputs that
made the step look like a git-push deploy. Name the deploy commit author
instead, and rename the step to match what it does.
The workflow now only runs on main, on a VERSION change. 0.1.20 is
already built and deployed, so bump so the merge deploys cleanly instead
of failing on git:from-image's "No changes detected".
Flagged by CodeQL on #1209: the workflow didn't restrict the token. The
job only needs to check out the repo.
@koechkevin
koechkevin requested a review from a team September 24, 2026 12:56

@kilemensi kilemensi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Comment thread pesacheck_meedan_bridge/py/main.py Outdated
…aCheck

Review feedback on #1209. py/VERSION isn't packaged into the pex, so the
User-Agent carries its own version rather than reading that file.
@koechkevin
koechkevin merged commit 11821de into main Sep 24, 2026
4 checks passed
@koechkevin
koechkevin deleted the chore/pesacheck-bridge-cd branch September 24, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants