Claude-code-plugins-plus-skills flyio-ci-integration
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/flyio-pack/skills/flyio-ci-integration" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-flyio-ci-integration && rm -rf "$T"
manifest:
plugins/saas-packs/flyio-pack/skills/flyio-ci-integration/SKILL.mdsource content
Fly.io CI Integration
Overview
Set up CI/CD for Fly.io edge deployments: run unit tests on every PR, deploy to staging on pull requests, and promote to production on merge to main. Fly.io uses Machines API for app management and deploy tokens for scoped CI authentication. CI pipelines build Docker images, deploy via
flyctl, and run post-deploy health checks against the edge endpoints.
GitHub Actions Workflow
# .github/workflows/fly-ci.yml name: Fly.io CI on: pull_request: branches: [main] push: branches: [main] jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm ci - run: npm test -- --reporter=verbose deploy: if: github.ref == 'refs/heads/main' needs: unit-tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: superfly/flyctl-actions/setup-flyctl@master - run: fly deploy --ha=false env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - name: Health check run: | sleep 10 curl -sf https://my-app.fly.dev/health || exit 1
Mock-Based Unit Tests
// tests/fly-service.test.ts import { describe, it, expect, vi } from 'vitest'; import { scaleApp } from '../src/fly-service'; vi.mock('../src/fly-client', () => ({ FlyClient: vi.fn().mockImplementation(() => ({ listMachines: vi.fn().mockResolvedValue([ { id: 'mch_abc', state: 'started', region: 'iad', config: { size: 'shared-cpu-1x' } }, { id: 'mch_def', state: 'started', region: 'lhr', config: { size: 'shared-cpu-1x' } }, ]), scaleMachine: vi.fn().mockResolvedValue({ id: 'mch_abc', state: 'started' }), getApp: vi.fn().mockResolvedValue({ name: 'my-app', status: 'deployed', hostname: 'my-app.fly.dev' }), })), })); describe('Fly.io Service', () => { it('scales app machines across regions', async () => { const result = await scaleApp('my-app', { count: 3 }); expect(result.machines).toBeDefined(); expect(result.status).toBe('scaled'); }); });
Integration Tests
// tests/integration/fly.integration.test.ts import { describe, it, expect } from 'vitest'; const hasToken = !!process.env.FLY_API_TOKEN; describe.skipIf(!hasToken)('Fly.io Live API', () => { it('lists apps via Machines API', async () => { const res = await fetch('https://api.machines.dev/v1/apps', { headers: { Authorization: `Bearer ${process.env.FLY_API_TOKEN}` }, }); expect(res.status).toBe(200); const body = await res.json(); expect(body).toHaveProperty('apps'); }); });
Error Handling
| CI Issue | Cause | Fix |
|---|---|---|
invalid | Token expired or revoked | Regenerate with |
| Deploy timeout | Image build too slow | Add Docker layer caching with |
| Health check fails | App not ready after deploy | Increase sleep or use |
Machine stuck in | Rolling deploy conflict | Run and destroy orphaned machines |
| Region unavailable | Edge region at capacity | Set in to a different region |
Resources
Next Steps
For deployment strategies, see
flyio-deploy-integration.