diff --git a/.github/workflows/cleanup.yaml b/.github/workflows/cleanup.yaml
deleted file mode 100644
index babefb7001a859e444fe330a8f09ba6f60da1d14..0000000000000000000000000000000000000000
--- a/.github/workflows/cleanup.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Cleanup
-
-on:
-  pull_request:
-    branches: [master]
-    types: [closed]
-
-jobs:
-  cleanup_staging_deploy:
-    name: Cleanup Staging Deploy
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v2
-
-    - name: Setup gcloud CLI
-      uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
-      with:
-        version: '285.0.0'
-        project_id: ${{ secrets.GCP_PROJECT_ID }}
-        service_account_email: ${{ secrets.GCP_SA_EMAIL }}
-        service_account_key: ${{ secrets.GCP_SA_KEY }}
-        export_default_credentials: true
-
-    - name: Setup deployment parameters
-      id: deployment_params
-      run: |
-        pr_number=$(jq -r .pull_request.number "$GITHUB_EVENT_PATH")
-        echo "::set-output name=version::pr${pr_number}"
-
-    - name: Remove deployment
-      run: gcloud app versions delete --project="${{ secrets.GCP_PROJECT_ID }}" "${{ steps.deployment_params.outputs.version }}"
diff --git a/.github/workflows/continuous_delivery.yaml b/.github/workflows/continuous_delivery.yaml
index 1897561d8f113999c40f964129c256f82ebd8569..91393595a610e795343c5abc046598150612aacf 100644
--- a/.github/workflows/continuous_delivery.yaml
+++ b/.github/workflows/continuous_delivery.yaml
@@ -1,15 +1,5 @@
 # This workflow implements continuous delivery with automated testing and fully
-# autonomous deploys to staging and production.
-#
-# If executed on a pull request, this will trigger a "staging" deploy to a
-# non-primary app engine version named "pr<number>" for the current pull
-# request number IFF tests pass.
-#
-# If executed on a push to master, this will trigger a "production" deploy IFF
-# tests pass.
-#
-# A separate "Cleanup" workflow (cleanup.yaml) runs when pull requests are
-# closed to remove the staging deploy for that pull request.
+# autonomous deploys to production on merge.
 name: CD
 
 # Translated: "Execute this workflow on pushes to master OR on pull requests
@@ -24,19 +14,6 @@ on:
     branches: [master]
 
 jobs:
-  lint:
-    name: Lint
-    runs-on: ubuntu-latest
-    steps:
-    - name: Setup
-      uses: actions/setup-go@v2
-
-    - name: Checkout
-      uses: actions/checkout@v2
-
-    - name: Lint
-      run: make lint
-
   test:
     name: Test
     runs-on: ubuntu-latest
@@ -50,8 +27,11 @@ jobs:
     - name: Build
       run: make build
 
+    - name: Lint
+      run: make lint
+
     - name: Test
-      run: make testci
+      run: git show && make testci
 
     - name: Code coverage
       uses: codecov/codecov-action@v1
@@ -81,95 +61,36 @@ jobs:
     - name: Test
       run: make test
 
-  staging_deploy:
-    name: Staging Deploy
-    if: github.ref != 'refs/heads/master'
-    runs-on: ubuntu-latest
-    needs: [lint, test]
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v2
-
-    - name: Setup gcloud CLI
-      uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
-      with:
-        version: '285.0.0'
-        project_id: ${{ secrets.GCP_PROJECT_ID }}
-        service_account_email: ${{ secrets.GCP_SA_EMAIL }}
-        service_account_key: ${{ secrets.GCP_SA_KEY }}
-        export_default_credentials: true
-
-    - name: Setup deployment parameters
-      id: deployment_params
-      run: |
-        pr_number=$(jq -r .pull_request.number "$GITHUB_EVENT_PATH")
-        echo "::set-output name=version::pr${pr_number}"
-
-    - name: Notify start
-      id: deployment
-      uses: bobheadxi/deployments@v0.4.0
-      with:
-        env: staging
-        ref: ${{ github.head_ref }}
-        step: start
-        token: ${{ secrets.GITHUB_TOKEN }}
-
-    - name: Deploy
-      run: gcloud app deploy --quiet --no-promote --project="${{ secrets.GCP_PROJECT_ID }}" --version="${{ steps.deployment_params.outputs.version }}"
-
-    - name: Notify finish
-      uses: bobheadxi/deployments@v0.4.0
-      with:
-        step: finish
-        deployment_id: ${{ steps.deployment.outputs.deployment_id }}
-        token: ${{ secrets.GITHUB_TOKEN }}
-        env: staging
-        status: ${{ job.status }}
-        env_url: 'https://${{ steps.deployment_params.outputs.version }}-dot-httpbingo.uc.r.appspot.com'
-        logs: 'https://console.cloud.google.com/logs/viewer?project=${{ secrets.GCP_PROJECT_ID }}&resource=gae_app%2Fmodule_id%2Fdefault%2Fversion_id%2F${{ steps.deployment_params.outputs.version }}'
-
   production_deploy:
     name: Production Deploy
     if: github.ref == 'refs/heads/master'
     runs-on: ubuntu-latest
-    needs: [lint, test]
+    needs: [test]
     steps:
     - name: Checkout
       uses: actions/checkout@v2
 
-    - name: Setup gcloud CLI
-      uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
-      with:
-        version: '285.0.0'
-        project_id: ${{ secrets.GCP_PROJECT_ID }}
-        service_account_email: ${{ secrets.GCP_SA_EMAIL }}
-        service_account_key: ${{ secrets.GCP_SA_KEY }}
-        export_default_credentials: true
-
-    - name: Setup deployment parameters
-      id: deployment_params
-      run: |
-        version=$(echo "${GITHUB_SHA}" | head -c 8)
-        echo "::set-output name=version::${version}"
-
     - name: Notify start
       id: deployment
-      uses: bobheadxi/deployments@v0.4.0
+      uses: bobheadxi/deployments@v0.4.2
       with:
         step: start
         token: ${{ secrets.GITHUB_TOKEN }}
-        env: staging
+        env: production
 
     - name: Deploy
-      run: gcloud app deploy --quiet --project="${{ secrets.GCP_PROJECT_ID }}" --version="${{ steps.deployment_params.outputs.version }}"
+      uses: superfly/flyctl-actions@1.1
+      with:
+        args: "deploy --strategy rolling"
+      env:
+        FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
 
     - name: Notify finish
-      uses: bobheadxi/deployments@v0.4.0
+      uses: bobheadxi/deployments@v0.4.2
       with:
         step: finish
         deployment_id: ${{ steps.deployment.outputs.deployment_id }}
         token: ${{ secrets.GITHUB_TOKEN }}
-        env: staging
+        env: production
         status: ${{ job.status }}
-        env_url: 'https://${{ steps.deployment_params.outputs.version }}-dot-httpbingo.uc.r.appspot.com'
-        logs: 'https://console.cloud.google.com/logs/viewer?project=${{ secrets.GCP_PROJECT_ID }}&resource=gae_app%2Fmodule_id%2Fdefault%2Fversion_id%2F${{ steps.deployment_params.outputs.version }}'
+        env_url: 'https://httpbingo.org'
diff --git a/Makefile b/Makefile
index d22faeefebecd94e24c6eda652dde23cf1eca24f..4531336c7f1d28360fa57c3e7205263847a76f0a 100644
--- a/Makefile
+++ b/Makefile
@@ -95,11 +95,21 @@ lint: $(TOOL_GOLINT) $(TOOL_STATICCHECK)
 run: build
 	$(DIST_PATH)/go-httpbin
 
+watch: $(TOOL_REFLEX)
+	reflex -s -r '\.(go|html)$$' make run
+
+
+# =============================================================================
+# deploy to fly.io
+# =============================================================================
+deploy:
+	flyctl deploy --strategy=rolling
+
 
 # =============================================================================
 # deploy to google cloud run
 # =============================================================================
-deploy: gcloud-auth imagepush
+deploy-cloud-run: gcloud-auth imagepush
 	$(GCLOUD_COMMAND) beta run deploy \
 		$(GCLOUD_PROJECT) \
 		--image=$(DOCKER_TAG_GCLOUD) \
@@ -109,8 +119,9 @@ deploy: gcloud-auth imagepush
 		--region=$(GCLOUD_REGION) \
 		--allow-unauthenticated \
 		--platform=managed
+	$(GCLOUD_COMMAND) run services update-traffic --to-latest
 
-stagedeploy: gcloud-auth imagepush
+stagedeploy-cloud-run: gcloud-auth imagepush
 	$(GCLOUD_COMMAND) beta run deploy \
 		$(GCLOUD_PROJECT) \
 		--image=$(DOCKER_TAG_GCLOUD) \
@@ -126,9 +137,6 @@ gcloud-auth:
 	@$(GCLOUD_COMMAND) auth list | grep '^\*' | grep -q $(GCLOUD_ACCOUNT) || $(GCLOUD_COMMAND) auth login $(GCLOUD_ACCOUNT)
 	@$(GCLOUD_COMMAND) auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://gcr.io
 
-watch: $(TOOL_REFLEX)
-	reflex -s -r '\.(go|html)$$' make run
-
 
 # =============================================================================
 # docker images
diff --git a/fly.toml b/fly.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b798550d545462d576f04c59aadc695555800996
--- /dev/null
+++ b/fly.toml
@@ -0,0 +1,21 @@
+app = "httpbingo"
+
+[[services]]
+  internal_port = 8080
+  protocol = "tcp"
+
+  [services.concurrency]
+    hard_limit = 25
+    soft_limit = 20
+
+  [[services.ports]]
+    handlers = ["http"]
+    port = "80"
+
+  [[services.ports]]
+    handlers = ["tls", "http"]
+    port = "443"
+
+  [[services.tcp_checks]]
+    interval = 10000
+    timeout = 2000