Skip to content

Continuous Integration & Deployment (CI/CD)

MicroFly provides native support for continuous deployment from CI/CD pipelines such as GitHub Actions, GitLab CI, or Forgejo.

You can deploy applications to MicroFly using any of three strategies:

  1. SSH Deployment (microfly deploy): Build artifacts inside GitHub Actions and ship them directly via SSH. Recommended for compiled languages (Go, Rust, C++) and Dockerless build matrices.
  2. Signed Webhooks (POST :8082/api/webhooks/<app>): Trigger MicroFly to pull the latest commit and rebuild. Recommended when you do not want to share SSH credentials with CI.
  3. Direct Git Push: Run git push microfly main from a CI runner.

This strategy builds the application inside GitHub Actions, transfers the release bundle to the MicroFly host, and executes microfly deploy.

The full workflow template is located at [.github/workflows/deploy.yml](file:///home/zhair/Documents/tools/microfly/.github/workflows/deploy.yml).

Step 1: Generate an SSH Deployment Keypair

On your local workstation or server:

bash
ssh-keygen -t ed25519 -C "github-actions-deploy" -f id_microfly_deploy -N ""

Add the public key (id_microfly_deploy.pub) to ~/.ssh/authorized_keys for the operator account (e.g. pi or ubuntu) on your MicroFly host:

bash
cat id_microfly_deploy.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Step 2: Grant Passwordless Sudo for MicroFly Commands

Workload management commands require root or sudo. To allow the deployment user to manage MicroFly without prompting for a password, create /etc/sudoers.d/microfly-deploy on the host:

bash
sudo tee /etc/sudoers.d/microfly-deploy << 'EOF'
pi ALL=(ALL) NOPASSWD: /usr/bin/microfly deploy *, /usr/bin/microfly status *, /usr/bin/microfly preview *
EOF
sudo chmod 0440 /etc/sudoers.d/microfly-deploy

Step 3: Configure GitHub Repository Secrets

In your GitHub repository, navigate to SettingsSecrets and variablesActions and add:

  • MICROFLY_HOST: Hostname or public/tailscale IP (e.g. 192.168.1.75 or paas.example.com).
  • MICROFLY_USER: SSH user (e.g. pi).
  • MICROFLY_SSH_KEY: The entire contents of the private key (id_microfly_deploy).

Step 4: Add Workflow Configuration

Create .github/workflows/deploy.yml:

yaml
name: Deploy to MicroFly

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Artifacts
        run: |
          go build -trimpath -ldflags "-s -w" -o server ./cmd/server

      - name: Configure SSH Key
        uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.MICROFLY_SSH_KEY }}

      - name: Add Server to Known Hosts
        run: |
          mkdir -p ~/.ssh
          ssh-keyscan -H ${{ secrets.MICROFLY_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy to MicroFly
        env:
          HOST: ${{ secrets.MICROFLY_HOST }}
          USER: ${{ secrets.MICROFLY_USER }}
          APP: my-app
        run: |
          STAGING="/tmp/deploy-$APP-${{ github.sha }}"
          ssh "$USER@$HOST" "mkdir -p $STAGING"
          rsync -avz --delete --exclude '.git' ./ "$USER@$HOST:$STAGING/"
          ssh "$USER@$HOST" "sudo microfly deploy --dir $STAGING $APP"
          ssh "$USER@$HOST" "rm -rf $STAGING"
          ssh "$USER@$HOST" "sudo microfly status $APP"

Strategy 2: Ephemeral Pull Request Previews

You can automatically spin up isolated preview environments for every Pull Request:

yaml
name: Ephemeral Preview

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        if: github.event.action != 'closed'

      - name: Configure SSH Key
        uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.MICROFLY_SSH_KEY }}

      - name: Add Server to Known Hosts
        run: |
          mkdir -p ~/.ssh
          ssh-keyscan -H ${{ secrets.MICROFLY_HOST }} >> ~/.ssh/known_hosts

      - name: Deploy Branch Preview
        if: github.event.action != 'closed'
        env:
          HOST: ${{ secrets.MICROFLY_HOST }}
          USER: ${{ secrets.MICROFLY_USER }}
          BRANCH: ${{ github.head_ref }}
        run: |
          STAGING="/tmp/preview-pr-${{ github.event.number }}"
          ssh "$USER@$HOST" "mkdir -p $STAGING"
          rsync -avz --delete --exclude '.git' ./ "$USER@$HOST:$STAGING/"
          ssh "$USER@$HOST" "sudo microfly deploy --dir $STAGING --branch $BRANCH --ttl 48h my-app"
          ssh "$USER@$HOST" "rm -rf $STAGING"

      - name: Sweep Expired Previews on PR Merge
        if: github.event.action == 'closed'
        env:
          HOST: ${{ secrets.MICROFLY_HOST }}
          USER: ${{ secrets.MICROFLY_USER }}
        run: |
          ssh "$USER@$HOST" "sudo microfly preview sweep"

Strategy 3: Deploy via HMAC-Signed Webhook

If you prefer not to manage SSH keys in CI, configure MicroFly's webhook receiver:

  1. In your application's app.toml:
    toml
    [webhook]
    enabled = true
    repository = "owner/my-app"
    branch = "main"
    secret = "env:GITHUB_WEBHOOK_SECRET"
  2. Store the webhook secret in MicroFly's encrypted store:
    bash
    printf 'my-strong-shared-secret' | sudo microfly secrets set my-app GITHUB_WEBHOOK_SECRET
  3. In GitHub Actions, trigger the deployment using curl and openssl:
    yaml
    - name: Trigger MicroFly Webhook
      env:
        URL: ${{ secrets.MICROFLY_WEBHOOK_URL }} # e.g. https://api.example.com/api/webhooks/my-app
        SECRET: ${{ secrets.MICROFLY_WEBHOOK_SECRET }}
      run: |
        PAYLOAD="{\"ref\":\"${{ github.ref }}\",\"repository\":{\"full_name\":\"${{ github.repository }}\"}}"
        SIG="sha256=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')"
        curl -fail -X POST "$URL" \
          -H "Content-Type: application/json" \
          -H "X-GitHub-Event: push" \
          -H "X-Hub-Signature-256: $SIG" \
          -d "$PAYLOAD"

Released under the MIT License.