diff --git a/.github/workflows/cleanup.yaml b/.github/workflows/cleanup.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8ce122fbe07de84ebf549d14774d2cfbee9f515d
--- /dev/null
+++ b/.github/workflows/cleanup.yaml
@@ -0,0 +1,32 @@
+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 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
new file mode 100644
index 0000000000000000000000000000000000000000..58ccdaf18879fc64b8616c467b1be9a8e7cd19b7
--- /dev/null
+++ b/.github/workflows/continuous_delivery.yaml
@@ -0,0 +1,171 @@
+# 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.
+name: CD
+
+# Translated: "Execute this workflow on pushes to master OR on pull requests
+# opened against master"
+#
+# See this question and answer for what we're solving here:
+# https://github.community/t5/GitHub-Actions/How-to-trigger-an-action-on-push-or-pull-request-but-not-both/m-p/36155#M2460
+on:
+  push:
+    branches: [master]
+  pull_request:
+    branches: [master]
+
+jobs:
+  lint:
+    name: Lint
+    runs-on: ubuntu-latest
+    steps:
+    - name: Setup
+      uses: actions/setup-go@v2
+      with:
+        go-version: '1.14'
+    - name: Checkout
+      uses: actions/checkout@v2
+    - name: Lint
+      run: make lint
+
+  test:
+    name: Test
+    runs-on: ubuntu-latest
+    steps:
+    - name: Setup
+      uses: actions/setup-go@v2
+      with:
+        go-version: '1.14'
+    - name: Checkout
+      uses: actions/checkout@v2
+    - name: Build
+      run: make build
+    - name: Test
+      run: make testci
+    - name: Code coverage
+      uses: codecov/codecov-action@v1
+      with:
+        file: ./coverage.txt
+
+  regression_test:
+    name: Regression Tests
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        go_version:
+        - '1.11'
+        - '1.12'
+        - '1.13'
+    steps:
+    - name: Setup
+      uses: actions/setup-go@v2
+      with:
+        go-version: ${{matrix.go_version}}
+    - name: Checkout
+      uses: actions/checkout@v2
+    - name: Build
+      run: make build
+    - 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]
+    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
+      with:
+        step: start
+        token: ${{ secrets.GITHUB_TOKEN }}
+        env: staging
+
+    - name: Deploy
+      run: gcloud app deploy --quiet --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 }}'
diff --git a/.github/workflows/continuous_integration.yaml b/.github/workflows/continuous_integration.yaml
deleted file mode 100644
index 96f3ebceb88bf90edd8d92c5539f0a8cd6725332..0000000000000000000000000000000000000000
--- a/.github/workflows/continuous_integration.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: CI
-
-on: [pull_request]
-
-jobs:
-  lint:
-    name: Lint
-    runs-on: ubuntu-latest
-    steps:
-    - name: Setup
-      uses: actions/setup-go@v2
-      with:
-        go-version: '1.14'
-    - name: Checkout
-      uses: actions/checkout@v2
-    - name: Lint
-      run: make lint
-
-  test:
-    name: Test
-    runs-on: ubuntu-latest
-    steps:
-    - name: Setup
-      uses: actions/setup-go@v2
-      with:
-        go-version: '1.14'
-    - name: Checkout
-      uses: actions/checkout@v2
-    - name: Test
-      run: make testci
-    - name: Code coverage
-      uses: codecov/codecov-action@v1
-      with:
-        file: ./coverage.txt
-
-  regression_test:
-    name: Regression Tests
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        go_version:
-        - '1.11'
-        - '1.12'
-        - '1.13'
-    steps:
-    - name: Setup
-      uses: actions/setup-go@v2
-      with:
-        go-version: ${{matrix.go_version}}
-    - name: Checkout
-      uses: actions/checkout@v2
-    - name: Test
-      run: make test