Skip to content

Latest commit

 

History

History
172 lines (125 loc) · 6.98 KB

File metadata and controls

172 lines (125 loc) · 6.98 KB

GitOps CI Template

Reusable GitHub Actions template for automated GitOps deployments. Polls GHCR for new image tags, creates PRs to update a .env file (consumed by docker-compose.yaml), and deploys on merge.

How it works

See the flow diagram in the root README.

  1. Trigger (ci-pr-trigger.yaml) — Runs on schedule, queries GHCR for the latest semver image tags using the custom composite action
  2. PR Creation (ci-create-release-pr.yaml) — Compares discovered tags with current .env file. Creates a PR only if versions differ
  3. Deployment — Triggers on push to the deploy branch when files in the project path change. Connects to the server and runs deployment.sh

Deployment Strategies

Three deployment workflow variants are included. Pick the one that matches your infrastructure:

Strategy Workflow Best for Networking
EC2 / AWS ci-deployment.yaml AWS infrastructure AWS credentials + SSM/Instance Connect
SSH ci-deployment-ssh.yaml Home servers, VPS, any Linux SSH key + reachable host
Self-Hosted Runner ci-deployment-self-hosted.yaml Home servers, on-prem None (runner runs on the server)

SSH (Home Server / VPS)

The SSH variant uses rsync + ssh to copy files and run deployment.sh on any reachable Linux machine. Several ways to make a home server reachable:

flowchart LR
    GH["GitHub Actions\nRunner"] --> decision{"How to reach\nhome server?"}

    decision --> A["Static IP /\nPort Forward"]
    decision --> B["Tailscale"]
    decision --> C["Cloudflare\nTunnel"]
    decision --> D["Self-Hosted\nRunner"]

    A --> SSH["SSH + rsync\n(ci-deployment-ssh.yaml)"]
    B --> SSH
    C --> SSH
    D --> LOCAL["Local execution\n(ci-deployment-self-hosted.yaml)"]

    style B stroke:#4ade80,stroke-width:2px
    style D stroke:#4ade80,stroke-width:2px
Loading

Option A: Tailscale (recommended) — No port forwarding, no static IP needed. The GitHub Actions runner joins your Tailscale network during the workflow and SSHs to your server via its Tailscale hostname. Uncomment the Tailscale step in ci-deployment-ssh.yaml and add a TAILSCALE_AUTHKEY secret.

Option B: Static IP / DDNS + port forwarding — Forward port 22 (or a custom port) on your router to your server. Use a DDNS service (DuckDNS, Cloudflare, etc.) if your IP changes. Set DEPLOY_HOST to the domain/IP.

Option C: Cloudflare Tunnel — Install cloudflared on your server to create a tunnel. The GitHub Actions runner uses cloudflared access ssh to connect without port forwarding.

Self-Hosted Runner (simplest for home servers)

The runner runs directly on your server — no SSH, no tunnels, no port forwarding. The deployment job checks out the repo and runs deployment.sh locally.

Setup:

  1. Install the GitHub Actions runner on your server
  2. Label it (e.g., homeserver)
  3. Replace {{RUNNER_LABEL}} in ci-deployment-self-hosted.yaml
  4. Delete the other deployment workflow files

Setup

1. Copy files into your repository

cp -r templates/gitops-ci/.github /path/to/your/repo/
cp templates/gitops-ci/deployment.sh /path/to/your/repo/projects/<name>/
cp templates/gitops-ci/docker-compose.yaml /path/to/your/repo/projects/<name>/
cp templates/gitops-ci/.env /path/to/your/repo/projects/<name>/

Then delete the deployment variants you don't need — keep only one of the three ci-deployment*.yaml files.

2. Replace all template placeholders

Search for {{...}} and replace. Which placeholders you need depends on the deployment strategy:

Common (all strategies):

Placeholder Description Example
{{ORG}} GitHub org name AxleResearch
{{BACKEND_IMAGE}} Backend image name my-app-backend
{{FRONTEND_IMAGE}} Frontend image name my-app-frontend
{{DEPLOY_BRANCH}} Branch that triggers deployment production
{{PROJECT_PATH}} Path to project in the repo projects/my-app
{{REMOTE_PATH}} Path on the target server /opt/my-app

EC2 only:

Placeholder Description Example
{{AWS_REGION}} AWS region us-east-1
{{INSTANCE_ID}} EC2 instance ID i-0abc123def456

SSH only:

Placeholder Description Example
{{REMOTE_USER}} SSH username deploy

Self-hosted runner only:

Placeholder Description Example
{{RUNNER_LABEL}} Self-hosted runner label homeserver

3. Configure repository secrets

Secret Strategy Required Description
AWS_ACCESS_KEY_ID EC2 Yes AWS credentials
AWS_SECRET_ACCESS_KEY EC2 Yes AWS credentials
DEPLOY_SSH_KEY SSH Yes Private SSH key for the server
DEPLOY_HOST SSH Yes Server address (IP, domain, or Tailscale hostname)
DEPLOY_HOST_KEY SSH Recommended Output of ssh-keyscan <host>
TAILSCALE_AUTHKEY SSH + Tailscale If using Tailscale Tailscale auth key
SLACK_WEBHOOK_URL All No Slack webhook for notifications

Files

.github/
  actions/
    ghcr-latest-tag/
      action.yaml                    # Custom action: query GHCR for latest semver tag
  workflows/
    ci-pr-trigger.yaml               # Cron job polling for new images
    ci-create-release-pr.yaml        # Reusable workflow: compare & create PR
    ci-deployment.yaml               # Deploy variant: EC2 / AWS
    ci-deployment-ssh.yaml           # Deploy variant: SSH (home server / VPS)
    ci-deployment-self-hosted.yaml   # Deploy variant: self-hosted runner
projects/<name>/
  .env                               # Image tags (BACKEND_TAG / FRONTEND_TAG)
  docker-compose.yaml                # Compose file (references tags from .env)
  deployment.sh                      # Deployment script

Customization

Single service (no frontend)

Remove the frontend-related steps from ci-pr-trigger.yaml and ci-create-release-pr.yaml. Remove the frontend service from docker-compose.yaml.

Multiple environments

Call ci-create-release-pr.yaml multiple times with different baseBranch and envPath inputs:

deploy-staging:
  uses: ./.github/workflows/ci-create-release-pr.yaml
  with:
    baseBranch: staging
    envPath: projects/my-app/staging/.env

deploy-production:
  uses: ./.github/workflows/ci-create-release-pr.yaml
  with:
    baseBranch: production
    envPath: projects/my-app/production/.env

Different polling intervals

Adjust the cron expression in ci-pr-trigger.yaml. Consider the cleanup job — higher frequency means more runs to clean up.

Interval Cron Runs/day
5 min */5 * * * * 288
15 min */15 * * * * 96
30 min */30 * * * * 48
1 hour 0 * * * * 24