Skip to content

feat: GitHub Actions deploy pipeline for the MVP host - #7

Merged
jhosepmyr merged 6 commits into
mainfrom
feature/mvp-deploy-pipeline
Oct 7, 2026
Merged

jhosepmyr merged 6 commits into
mainfrom
feature/mvp-deploy-pipeline

Conversation

@jhosepmyr

@jhosepmyr jhosepmyr commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Continuous deployment for the single-EC2 MVP (https://reqsai.tech) with GitHub Actions. It automates the registry-free flow from #6 (build linux/arm64 image archives, upload them with Ansible, docker load, docker compose up), and it costs nothing extra: free arm64 runners for public repos, one-day artifacts, and IAM/OIDC only. Port 22 stays closed to the internet: the runner reaches the host through SSH tunnelled over SSM.

#6 is already merged, so this PR targets main directly.

What changes

Terraform (envs/ec2-compose/github-deploy.tf, already applied)

  • GitHub OIDC provider token.actions.githubusercontent.com.
  • Role reqsai-mvp-github-deploy. Only a token with sub = repo:Kntro-Soft/reqsai-infra:environment:mvp and aud = sts.amazonaws.com can assume it.
  • Inline policy, nothing else:
    • ssm:StartSession on this instance with the AWS-StartSSHSession document only, plus ssm:SessionDocumentAccessCheck, so the role cannot open the default shell document.
    • ssm:TerminateSession, ssm:ResumeSession and ssmmessages:OpenDataChannel on sessions named reqsai-mvp-deploy-*, the role session name the workflow sets.
    • ec2:DescribeInstances.
  • New variables github_deploy_repository, github_deploy_environment and github_oidc_provider_arn; new output github_deploy_role_arn.

Ansible

  • base: base_authorized_keys list, applied by a task tagged authorized_keys. It only adds keys.
  • app (archive.yml): uploads only the archives present in app_images_archive_dir, then requires both reqsai-*:archive images on the host. This lets a run deploy one app, or only configuration, and keep the other image that is already loaded.

Workflow .github/workflows/deploy-mvp.yml

  • Triggers:
    • workflow_dispatch with api_ref and web_ref, both defaulting to main;
    • repository_dispatch of type deploy-mvp, with optional refs in client_payload that also default to main;
    • push to main on ansible/**, compose/** or the workflow. This deploys keep/keep, which re-applies configuration without rebuilding or changing app versions.
  • concurrency: deploy-mvp with no cancellation.
  • refs validates the refs and builds the matrix. build runs on ubuntu-24.04-arm and does docker buildx build --platform linux/arm64 --load, then docker save | gzip -1, then uploads an artifact kept for one day. Images carry the OCI labels source and revision.
  • deploy runs on ubuntu-latest in the mvp environment:
    • assumes the role with OIDC;
    • installs the official Session Manager .deb, pinned to version 1.2.835.0 with its SHA-256 checked;
    • installs ansible-core 2.21.5 and the collections;
    • writes the secrets to a umask 077 temp dir;
    • runs ansible-playbook site.yml --tags app against the instance ID through an SSM ProxyCommand;
    • requires UP from https://reqsai.tech/actuator/health;
    • deletes the temp dir.

Docs: new section 14 "Despliegue continuo con GitHub Actions" in docs/deploy-ec2-docker-compose.md. It covers the architecture, secrets and variables, how to run a deploy, rollback by deploying an older ref, the PAT for auto-deploy from the app repos, and troubleshooting.

Configured outside the repo (done)

  • terraform apply in envs/ec2-compose: 3 resources added (OIDC provider, role, inline policy). After the first run showed that session IDs are <role-session-name>-<random>, a second apply changed the inline policy in place (1 changed) to scope sessions to reqsai-mvp-deploy-*.
  • Dedicated deploy key reqsai-mvp-github-deploy (ed25519). It is authorized on the host with --tags authorized_keys, using from="127.0.0.1,::1",no-agent-forwarding,no-port-forwarding,no-X11-forwarding. SSM connects to sshd from localhost, so the key only works through the SSM tunnel. The private key exists only as an environment secret.
  • Environment mvp. Deployment branches: main only. It was temporarily opened to this branch for validation, and that permission has been removed.
    • Secrets: ANSIBLE_VAULT_PASSWORD, ANSIBLE_VAULT_B64, ANSIBLE_EXTRA_VARS_B64, DEPLOY_SSH_PRIVATE_KEY.
    • Variables: AWS_DEPLOY_ROLE_ARN, AWS_REGION, EC2_INSTANCE_ID, APP_URL.

Validation (real runs against production)

The main branches of reqsai-api and reqsai-web do not contain the MVP code yet, so main was not deployed. A temporary commit, now dropped from this branch, added a push trigger for this branch.

  1. Full deploy, https://github.com/Kntro-Soft/reqsai-infra/actions/runs/37627951635, success in 11m23s:
    • api_ref=deploy/mvp-preview: a69aac3c, the integration of reqsai-api #89 and #90 that was already running, and the same tree as develop now.
    • web_ref=feature/mvp-ux-polish: 410c4f5, a descendant of the commit that was running and identical to develop now.
    • Builds took 2m07s for the API and 34s for the web, in parallel. Ansible took 8m14s; uploading ~300 MB through SSM runs at ~0.8 MB/s.
    • Host: new reqsai-api:archive and reqsai-web:archive images whose labels show those SHAs, and api/web recreated at 13:32:51Z. db and caddy were untouched, and the config files were unchanged (ok).
    • CloudTrail: the role's StartSession succeeded with no AccessDenied.
  2. Config-only (keep/keep), https://github.com/Kntro-Soft/reqsai-infra/actions/runs/37629813006, success in 49s. It ran with changed=0 and no container was recreated. This is what the merge of this PR triggers.

https://reqsai.tech/actuator/health returns UP.

After merging

…e MVP host

The role trusts only repo:Kntro-Soft/reqsai-infra:environment:mvp. It may
start AWS-StartSSHSession sessions on this instance (with the session
document access check, so no default shell), use sessions whose ID starts
with the deploy role session name, and describe instances.
Only archives present in app_images_archive_dir are uploaded. The role then
requires both archive images on the host, so a config-only run or a
single-app deploy reuses the image that is already loaded.
…em over SSM

Builds reqsai-api and reqsai-web for linux/arm64 on free arm64 runners,
ships them as artifacts and runs the app role through SSH tunnelled over
SSM with an OIDC role, then requires UP from /actuator/health. Pushes to
main that touch ansible/ or compose/ redeploy the configuration only.
@jhosepmyr
jhosepmyr merged commit eb836d6 into main Oct 7, 2026
2 checks passed
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.

1 participant