Skip to content

Commit ab3b3c5

Browse files
PAMulliganPaul Mulliganclaude
authored
feat: add Fly.io deployment target (#30) (#118)
Add --fly to setup-project.sh alongside --cloudflare, --node, --lambda, and --railway. Fly.io deploys the same multi-stage Docker image as the Node.js target, so the runtime code is shared: the fly platform reuses the node snippets and node-server Dockerfile/compose, adding fly.toml on top. - templates/fly/fly.toml: app name placeholder, HTTP service on internal_port 3000 with force_https, /health check gating each deploy, autostop/autostart scaling (min_machines_running = 1, ceiling set with `fly scale count 3` for min 1 / max 3), Managed Postgres attachment reference (fly mpg attach sets DATABASE_URL), commented release_command with the pruned-dev-deps caveat - setup-project.sh: --fly flag, .env.example with flyctl secrets notes, README deployment instructions (fly launch, mpg create/attach, secrets, deploys, scaling, migrations via fly mpg proxy) - No deploy workflow generated: `fly deploy` is the primary flow; continuous deployment via GitHub Actions + FLY_API_TOKEN is linked - CI: require templates/fly; no new snippets to type-check - deployment-config-generator (Step 2e) and schema-intake skills, pipeline.config.json, and docs updated for the fifth target All fly.toml keys verified against the live fly.toml reference; all linked Fly.io doc URLs return 200. End-to-end: setup-project.sh my-api --fly generates a project whose test suite passes (20/20). Closes #30 Co-authored-by: Paul Mulligan <paul@pmds.pull-list.net> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent f8b549d commit ab3b3c5

14 files changed

Lines changed: 299 additions & 31 deletions

File tree

.claude/CUSTOM-AGENTS-GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Integration testing, contract testing, load testing for APIs.
8989

9090
### devops-automator (blue)
9191
Docker, CI/CD pipeline generation, deployment automation.
92-
- **When to use:** Setting up Docker, creating CI/CD, deploying to Cloudflare Workers, Node.js, AWS Lambda, or Railway
92+
- **When to use:** Setting up Docker, creating CI/CD, deploying to Cloudflare Workers, Node.js, AWS Lambda, Railway, or Fly.io
9393
- **Key capabilities:** Multi-stage Docker, GitHub Actions, wrangler deploy, PM2, health endpoints
9494

9595
---

.claude/agents/devops-automator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: devops-automator
3-
description: API deployment and CI/CD specialist for Docker, GitHub Actions, Cloudflare Workers, Node.js, AWS Lambda, and Railway deployments. Use when setting up deployment pipelines, configuring Docker, managing Workers deployments, deploying with SAM, configuring Railway services, or automating build processes.
3+
description: API deployment and CI/CD specialist for Docker, GitHub Actions, Cloudflare Workers, Node.js, AWS Lambda, Railway, and Fly.io deployments. Use when setting up deployment pipelines, configuring Docker, managing Workers deployments, deploying with SAM, configuring Railway services, deploying to Fly.io Machines, or automating build processes.
44
color: blue
55
tools: Bash, Read, Write, Grep, MultiEdit
66
---

.claude/pipeline.config.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"noAnyTypes": true
6969
},
7070
"deployment": {
71-
"targets": ["cloudflare-workers", "node-docker", "aws-lambda", "railway"],
71+
"targets": ["cloudflare-workers", "node-docker", "aws-lambda", "railway", "fly"],
7272
"defaultTarget": "cloudflare-workers",
7373
"cloudflareWorkers": {
7474
"compatibilityDate": "2024-12-01",
@@ -105,6 +105,17 @@
105105
"managedPostgres": true,
106106
"autoDeployFromGitHub": true
107107
},
108+
"fly": {
109+
"builder": "dockerfile",
110+
"internalPort": 3000,
111+
"healthcheckPath": "/health",
112+
"autoStopMachines": "stop",
113+
"autoStartMachines": true,
114+
"minMachinesRunning": 1,
115+
"maxMachines": 3,
116+
"managedPostgres": true,
117+
"autoDeployFromGitHub": false
118+
},
108119
"cicd": {
109120
"provider": "github-actions",
110121
"runTests": true,

.claude/skills/deployment-config-generator/SKILL.md

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ description: >
66
For Node.js, generates Dockerfile and docker-compose.yml with PostgreSQL. For
77
AWS Lambda, generates a SAM template (API Gateway HTTP API), esbuild bundling
88
config, and an OIDC deploy workflow. For Railway, generates railway.toml
9-
(Dockerfile build, health check, start command) and nixpacks.toml. Always
10-
generates GitHub Actions CI/CD workflow and .env.example. Keywords: deploy,
11-
deployment, wrangler, cloudflare, workers, docker, dockerfile, docker-compose,
12-
aws, lambda, sam, api-gateway, serverless, esbuild, oidc, cold-start, railway,
13-
nixpacks, paas, github-actions, ci-cd, pipeline, env, configuration,
14-
infrastructure
9+
(Dockerfile build, health check, start command) and nixpacks.toml. For
10+
Fly.io, generates fly.toml (HTTP service, health check, Machine autoscaling,
11+
Managed Postgres attachment). Always generates GitHub Actions CI/CD workflow
12+
and .env.example. Keywords: deploy, deployment, wrangler, cloudflare,
13+
workers, docker, dockerfile, docker-compose, aws, lambda, sam, api-gateway,
14+
serverless, esbuild, oidc, cold-start, railway, nixpacks, paas, fly, flyio,
15+
flyctl, machines, fly-toml, github-actions, ci-cd, pipeline, env,
16+
configuration, infrastructure
1517
---
1618

1719
# Deployment Config Generator (Phase 5)
@@ -47,7 +49,7 @@ const spec = JSON.parse(
4749
await readFile('.claude/plans/build-spec.json', 'utf-8'),
4850
);
4951

50-
const target = spec.deployment.target; // 'cloudflare-workers' | 'node' | 'aws-lambda' | 'railway'
52+
const target = spec.deployment.target; // 'cloudflare-workers' | 'node' | 'aws-lambda' | 'railway' | 'fly'
5153
const authStrategy = spec.auth.strategy;
5254
```
5355

@@ -410,6 +412,74 @@ cmds = ["pnpm build"]
410412
cmd = "node dist/index.js"
411413
```
412414

415+
### Step 2e -- Fly.io Configuration
416+
417+
If target is `fly`, reuse the Node.js Dockerfile from Step 2b and generate
418+
`fly.toml`. The canonical template lives in `templates/fly/fly.toml`. Fly.io
419+
builds Machines from the Dockerfile, so the deployed image is identical to
420+
the local `docker compose up` image.
421+
422+
```toml
423+
# api/fly.toml
424+
app = "my-api" # globally unique; fly launch sets it
425+
primary_region = "iad"
426+
427+
[build]
428+
dockerfile = "Dockerfile"
429+
430+
[env]
431+
NODE_ENV = "production"
432+
PORT = "3000"
433+
LOG_LEVEL = "info"
434+
435+
[http_service]
436+
internal_port = 3000
437+
force_https = true
438+
auto_stop_machines = "stop"
439+
auto_start_machines = true
440+
min_machines_running = 1
441+
442+
[http_service.concurrency]
443+
type = "requests"
444+
soft_limit = 200
445+
hard_limit = 250
446+
447+
[[http_service.checks]]
448+
method = "GET"
449+
path = "/health"
450+
interval = "15s"
451+
timeout = "5s"
452+
grace_period = "10s"
453+
454+
[[vm]]
455+
size = "shared-cpu-1x"
456+
memory = "512mb"
457+
```
458+
459+
Autoscaling is autostop/autostart: Fly stops idle Machines down to
460+
`min_machines_running` and restarts them when traffic crosses the concurrency
461+
`soft_limit`. The ceiling is the number of Machines that exist -- instruct the
462+
user to run `fly scale count 3` for min 1 / max 3.
463+
464+
Secrets are set with flyctl, not committed (`[env]` holds non-secrets only).
465+
Attaching Managed Postgres sets `DATABASE_URL` automatically:
466+
467+
```bash
468+
fly mpg create # managed PostgreSQL cluster
469+
fly mpg attach <cluster-id> -a <app> # sets DATABASE_URL secret
470+
fly secrets set JWT_SECRET=$(openssl rand -hex 32)
471+
```
472+
473+
No deploy workflow is generated by default: `fly deploy` from `api/` builds
474+
and deploys, gated by the `/health` check. Fly.io does not auto-deploy from
475+
GitHub -- if the user wants continuous deployment, point them at the
476+
[GitHub Actions guide](https://fly.io/docs/launch/continuous-deployment-with-github-actions/)
477+
(`flyctl deploy --remote-only` with a `FLY_API_TOKEN` secret).
478+
479+
Migrations run from the developer's machine through `fly mpg proxy` (the
480+
production image prunes dev dependencies, so a `release_command` migration
481+
would need drizzle-kit added to the image first -- see the fly.toml comments).
482+
413483
### Step 3 -- Generate GitHub Actions CI/CD
414484

415485
```yaml
@@ -514,6 +584,11 @@ jobs:
514584
# - run: cd api && pnpm run build:lambda && sam deploy --no-confirm-changeset
515585
# For Railway: no deploy step -- Railway auto-deploys the connected
516586
# GitHub repo on push, gated by the /health check (see Step 2d).
587+
# For Fly.io (requires a FLY_API_TOKEN secret, see Step 2e):
588+
# - uses: superfly/flyctl-actions/setup-flyctl@master
589+
# - run: cd api && flyctl deploy --remote-only
590+
# env:
591+
# FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
517592

518593
deploy-production:
519594
runs-on: ubuntu-latest
@@ -592,6 +667,7 @@ Ensure the API package.json has all necessary scripts:
592667
| esbuild config | `api/esbuild.config.mjs` | Single-file Lambda bundle build |
593668
| Railway config | `api/railway.toml` | Build, health check, start command, restart policy |
594669
| Nixpacks config | `api/nixpacks.toml` | Toolchain pins for the non-Docker Railway build |
670+
| Fly.io config | `api/fly.toml` | HTTP service, health check, Machine autoscaling |
595671
| CI/CD workflow | `.github/workflows/ci.yml` | GitHub Actions pipeline |
596672
| Deploy workflow | `.github/workflows/deploy.yml` | OIDC-based AWS Lambda deploy (Lambda target) |
597673
| Env template | `.env.example` | Environment variable template |

.claude/skills/schema-intake/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ continuing. Adapt follow-up questions based on responses.
8484
8585
**Question 6 -- Deployment Target**
8686
> Where will this API be deployed?
87-
> Options: Cloudflare Workers (default), Node.js / Docker, AWS Lambda, Railway
87+
> Options: Cloudflare Workers (default), Node.js / Docker, AWS Lambda, Railway, Fly.io
8888
8989
**Question 7 -- Additional Requirements** (optional)
9090
> Any special requirements? (pagination style, rate limiting, soft deletes, etc.)
@@ -294,7 +294,7 @@ interface BuildSpec {
294294
sourceType: 'file' | 'interview';
295295
};
296296
deployment: {
297-
target: 'cloudflare-workers' | 'node' | 'aws-lambda' | 'railway';
297+
target: 'cloudflare-workers' | 'node' | 'aws-lambda' | 'railway' | 'fly';
298298
runtime: string;
299299
};
300300
auth: {
@@ -553,7 +553,7 @@ function validateBuildSpec(spec: BuildSpec): string[] {
553553
| `tdd-from-schema` (Phase 2) | Reads `endpoints` and `auth` to generate test cases |
554554
| `route-generation` (Phase 3) | Reads `endpoints` to scaffold Hono route handlers |
555555
| `api-authentication` | Reads `auth` to configure JWT/API key middleware |
556-
| `deployment-config-generator` | Reads `deployment` to generate wrangler.toml, Dockerfile, SAM template, or railway.toml |
556+
| `deployment-config-generator` | Reads `deployment` to generate wrangler.toml, Dockerfile, SAM template, railway.toml, or fly.toml |
557557
| `api-documentation` | Reads the generated OpenAPI spec or regenerates from build-spec |
558558

559559
### Triggering the Next Phase

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ jobs:
166166
fi
167167
done
168168
169-
for dir in templates/shared templates/cloudflare-workers templates/node-server templates/docker templates/aws-lambda templates/railway; do
169+
for dir in templates/shared templates/cloudflare-workers templates/node-server templates/docker templates/aws-lambda templates/railway templates/fly; do
170170
if [ -d "$dir" ]; then
171171
echo " $dir: OK"
172172
else

CLAUDE.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
This is a **Claude Code-integrated API & backend development framework** providing specialized agents, skills, scripts, and schema-to-API conversion pipelines. Built with TypeScript, Hono (HTTP framework), Drizzle ORM, and PostgreSQL. Deployed to Cloudflare Workers, Node.js, AWS Lambda, or Railway.
7+
This is a **Claude Code-integrated API & backend development framework** providing specialized agents, skills, scripts, and schema-to-API conversion pipelines. Built with TypeScript, Hono (HTTP framework), Drizzle ORM, and PostgreSQL. Deployed to Cloudflare Workers, Node.js, AWS Lambda, Railway, or Fly.io.
88

99
Nerva is the backend counterpart to **Aurelius** (frontend framework).
1010

1111
The framework is designed for:
1212
- Schema-first API development (OpenAPI spec to working API)
1313
- OpenAPI-to-working-API conversion with TDD-mandatory development
1414
- Comprehensive testing (integration tests, contract tests, load tests with k6)
15-
- Deployment to Cloudflare Workers, Node.js (Docker), AWS Lambda (SAM), or Railway
15+
- Deployment to Cloudflare Workers, Node.js (Docker), AWS Lambda (SAM), Railway, or Fly.io
1616
- Full product lifecycle support (engineering, database, testing, DevOps, operations)
1717

1818
## Project Structure
@@ -40,6 +40,7 @@ project-root/
4040
│ ├── node-server/ # Node.js server config
4141
│ ├── aws-lambda/ # SAM template, esbuild config, OIDC deploy workflow
4242
│ ├── railway/ # Railway config-as-code (railway.toml, nixpacks.toml)
43+
│ ├── fly/ # Fly.io config (fly.toml)
4344
│ └── docker/ # Dockerfile, docker-compose
4445
├── docs/
4546
│ ├── schema-to-api/ # Pipeline guide
@@ -59,7 +60,7 @@ project-root/
5960

6061
```bash
6162
# Setup a new API project
62-
./scripts/setup-project.sh my-api --cloudflare # or --node / --lambda / --railway
63+
./scripts/setup-project.sh my-api --cloudflare # or --node / --lambda / --railway / --fly
6364

6465
# Run tests with coverage
6566
./scripts/run-tests.sh
@@ -234,7 +235,7 @@ Autonomous 10-phase pipeline that converts an OpenAPI specification into a worki
234235
- **Schema-first** — OpenAPI spec drives database schema, types, handlers, and tests
235236
- **Contract testing** — generated tests validate API responses against OpenAPI spec
236237
- **Load testing** — k6 scripts generated for performance-critical endpoints
237-
- **Multi-target deployment** — Cloudflare Workers, Node.js, Docker, AWS Lambda, Railway with single config change
238+
- **Multi-target deployment** — Cloudflare Workers, Node.js, Docker, AWS Lambda, Railway, Fly.io with single config change
238239
- **Type safety end-to-end** — Drizzle schema to Zod validators to TypeScript types
239240
- Quality gate: 80%+ coverage, TypeScript strict, security audit, bundle analysis
240241
- Resumable: TodoWrite tracks progress across interrupted sessions
@@ -491,9 +492,10 @@ pnpm drizzle-kit studio # Open database GUI
491492
./scripts/setup-project.sh my-api --node # New Node.js project
492493
./scripts/setup-project.sh my-api --lambda # New AWS Lambda (SAM) project
493494
./scripts/setup-project.sh my-api --railway # New Railway project
495+
./scripts/setup-project.sh my-api --fly # New Fly.io project
494496
```
495497

496498
---
497499

498-
**Last Updated:** 2026-06-11
500+
**Last Updated:** 2026-06-12
499501
**Architecture:** 24 agents, 12 skills, 4 plugins + gh CLI, 9 scripts

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ A Claude Code-integrated API & backend development framework with TypeScript, Ho
1717
- **12 Development Skills** — Automated workflows for schema parsing, TDD, route generation, authentication, validation, documentation
1818
- **10-Phase Schema-to-API Pipeline** — Convert OpenAPI specs into fully working, tested API servers with a single command
1919
- **TDD-Mandatory Development** — Integration tests written before route handlers, hard gate enforced
20-
- **Four Deployment Targets** — Cloudflare Workers (edge), Node.js (Docker), AWS Lambda (SAM), or Railway (PaaS) with one config change
20+
- **Five Deployment Targets** — Cloudflare Workers (edge), Node.js (Docker), AWS Lambda (SAM), Railway (PaaS), or Fly.io (global containers) with one config change
2121
- **Testing Stack** — Vitest, supertest, contract tests against OpenAPI, k6 load tests
2222
- **Schema-First Design** — Database schemas, types, and validators all derived from your API spec
2323

@@ -37,7 +37,7 @@ cd Nerva
3737
pnpm install
3838

3939
# Initialize a new API project
40-
./scripts/setup-project.sh my-api --cloudflare # or --node / --lambda / --railway
40+
./scripts/setup-project.sh my-api --cloudflare # or --node / --lambda / --railway / --fly
4141

4242
# Start development
4343
cd api && pnpm dev
@@ -177,6 +177,7 @@ Full catalog: [`.claude/CUSTOM-AGENTS-GUIDE.md`](.claude/CUSTOM-AGENTS-GUIDE.md)
177177
| `templates/node-server/` | Dockerfile + docker-compose with PostgreSQL |
178178
| `templates/aws-lambda/` | SAM template, esbuild config, OIDC deploy workflow |
179179
| `templates/railway/` | Railway config-as-code + Nixpacks build config |
180+
| `templates/fly/` | Fly.io config (HTTP service, health check, autoscaling) |
180181
| `templates/docker/` | Extended Docker with Redis + pgAdmin |
181182

182183
## Part of the PMDS Framework Series

docs/onboarding/architecture.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Shell scripts for common development tasks:
6161

6262
| Script | Purpose |
6363
|--------|---------|
64-
| `setup-project.sh` | Initialize a new API project (--cloudflare, --node, --lambda, or --railway) |
64+
| `setup-project.sh` | Initialize a new API project (--cloudflare, --node, --lambda, --railway, or --fly) |
6565
| `run-tests.sh` | Vitest test suite with coverage |
6666
| `check-types.sh` | TypeScript strict mode checking |
6767
| `security-scan.sh` | Dependency and code security audit |
@@ -71,7 +71,7 @@ Shell scripts for common development tasks:
7171
| `generate-openapi-docs.sh` | Build API documentation |
7272
| `generate-client.sh` | Generate typed API client for frontends |
7373

74-
### Templates (6 directories)
74+
### Templates (7 directories)
7575

7676
Starter configurations for different deployment targets:
7777

@@ -82,6 +82,7 @@ Starter configurations for different deployment targets:
8282
| `templates/node-server/` | Dockerfile + docker-compose with PostgreSQL |
8383
| `templates/aws-lambda/` | SAM template (API Gateway HTTP API), esbuild config, OIDC deploy workflow |
8484
| `templates/railway/` | Railway config-as-code (Docker build, health check) + Nixpacks config |
85+
| `templates/fly/` | Fly.io config (HTTP service, health check, Machine autoscaling) |
8586
| `templates/docker/` | Extended Docker setup with Redis + pgAdmin |
8687

8788
## Pipelines

docs/onboarding/quickstart.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ If you have an OpenAPI specification file:
3232

3333
```bash
3434
# Initialize the project structure
35-
./scripts/setup-project.sh my-api --cloudflare # or --node / --lambda / --railway
35+
./scripts/setup-project.sh my-api --cloudflare # or --node / --lambda / --railway / --fly
3636

3737
# Open Claude Code and run the pipeline
3838
/build-from-schema path/to/openapi-spec.yaml

0 commit comments

Comments
 (0)