|
| 1 | +# =============================================================== |
| 2 | +# ☁️ MCP Gateway ▸ Build, Cache & Deploy to Google Cloud Run |
| 3 | +# =============================================================== |
| 4 | +# Maintainer: Mihai Criveti |
| 5 | +# Status: Inactive |
| 6 | +# This workflow: |
| 7 | +# • Restores / updates a local **BuildKit layer cache** ❄️ |
| 8 | +# • Builds the Docker image from **Containerfile.lite** 🏗️ |
| 9 | +# • Pushes the image to **Google Artifact Registry** 📤 |
| 10 | +# • Deploys to **Google Cloud Run** with autoscale=1 🚀 |
| 11 | +# |
| 12 | +# --------------------------------------------------------------- |
| 13 | +# Prerequisites (one-time setup) |
| 14 | +# --------------------------------------------------------------- |
| 15 | +# 1. Create a Google Cloud project and enable billing |
| 16 | +# 2. Enable required APIs: |
| 17 | +# gcloud services enable run.googleapis.com artifactregistry.googleapis.com |
| 18 | +# 3. Create an Artifact Registry repository: |
| 19 | +# gcloud artifacts repositories create mcpgateway \ |
| 20 | +# --repository-format=docker \ |
| 21 | +# --location=us-central1 \ |
| 22 | +# --description="MCP Gateway Docker images" |
| 23 | +# 4. Create a service account with required permissions: |
| 24 | +# - Cloud Run Developer (only on mcpgateway service) |
| 25 | +# - Artifact Registry Writer (only on mcpgateway repository) |
| 26 | +# Example for restricted setup: |
| 27 | +# gcloud run services add-iam-policy-binding mcpgateway \ |
| 28 | +# --region=us-central1 \ |
| 29 | +# --member="serviceAccount: [email protected]" \ |
| 30 | +# --role="roles/run.developer" |
| 31 | +# 5. Create Cloud SQL (PostgreSQL) and Memorystore (Redis) instances |
| 32 | +# (see deployment documentation for commands) |
| 33 | +# |
| 34 | +# --------------------------------------------------------------- |
| 35 | +# Required repository **secrets** |
| 36 | +# --------------------------------------------------------------- |
| 37 | +# ┌────────────────────────┬─────────────────────────────────────────────────────────────────────┐ |
| 38 | +# │ Secret name │ Description / Example value │ |
| 39 | +# ├────────────────────────┼─────────────────────────────────────────────────────────────────────┤ |
| 40 | +# │ GCP_PROJECT_ID │ Your Google Cloud project identifier. │ |
| 41 | +# │ │ While not a secret, it's sensitive and used as such for consistency.│ |
| 42 | +# │ │ Example: "my-gcp-project-123456" │ |
| 43 | +# ├────────────────────────┼─────────────────────────────────────────────────────────────────────┤ |
| 44 | +# │ GCP_SERVICE_KEY │ Service account JSON key for authentication to Google Cloud │ |
| 45 | +# │ │ Get from: IAM & Admin > Service Accounts > Keys > Add Key > JSON │ |
| 46 | +# │ │ Example: {"type": "service_account", "project_id": "my-project"...} │ |
| 47 | +# ├────────────────────────┼─────────────────────────────────────────────────────────────────────┤ |
| 48 | +# │ JWT_SECRET_KEY │ Random secret for signing JWT authentication tokens │ |
| 49 | +# │ │ Generate with: openssl rand -base64 32 │ |
| 50 | +# │ │ Example: "xQ3Z5yDvEd2qP9kL7mN4wR8tU1aF6jH0bC3gI5sV" │ |
| 51 | +# ├────────────────────────┼─────────────────────────────────────────────────────────────────────┤ |
| 52 | +# │ BASIC_AUTH_PASSWORD │ Password for HTTP Basic Authentication (fallback auth method) │ |
| 53 | +# │ │ Example: "changeme-to-something-secure" │ |
| 54 | +# ├────────────────────────┼─────────────────────────────────────────────────────────────────────┤ |
| 55 | +# │ DATABASE_URL │ PostgreSQL connection string for Cloud SQL instance │ |
| 56 | +# │ │ Format: postgresql://USER:PASS@IP:PORT/DATABASE │ |
| 57 | +# │ │ Example: "postgresql://postgres: [email protected]:5432/mcpgw" │ |
| 58 | +# ├────────────────────────┼─────────────────────────────────────────────────────────────────────┤ |
| 59 | +# │ REDIS_URL │ Redis connection string for Memorystore instance │ |
| 60 | +# │ │ Format: redis://IP:PORT/DB_NUMBER │ |
| 61 | +# │ │ Example: "redis://10.20.30.50:6379/0" │ |
| 62 | +# └────────────────────────┴─────────────────────────────────────────────────────────────────────┘ |
| 63 | +# |
| 64 | +# --------------------------------------------------------------- |
| 65 | +# Required repository **variables** |
| 66 | +# --------------------------------------------------------------- |
| 67 | +# ┌────────────────────────────┬─────────────────────────────────────────────────────────────────┐ |
| 68 | +# │ Variable name │ Description / Example value │ |
| 69 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 70 | +# │ GCP_REGION │ Google Cloud region for deployment │ |
| 71 | +# │ │ Example: "us-central1" (or us-east1, europe-west1, etc.) │ |
| 72 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 73 | +# │ GAR_REPO_NAME │ Artifact Registry repository name (must exist) │ |
| 74 | +# │ │ Example: "mcpgateway" or "docker-images" │ |
| 75 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 76 | +# │ IMAGE_NAME │ Name for your Docker image │ |
| 77 | +# │ │ Example: "mcpgateway" │ |
| 78 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 79 | +# │ CLOUD_RUN_SERVICE │ Name of the Cloud Run service │ |
| 80 | +# │ │ Example: "mcpgateway" or "mcp-gateway-prod" │ |
| 81 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 82 | +# │ CLOUD_RUN_PORT │ Port number the container listens on (numeric, no quotes) │ |
| 83 | +# │ │ Example: 4444 │ |
| 84 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 85 | +# │ BASIC_AUTH_USER │ Username for HTTP Basic Authentication │ |
| 86 | +# │ │ Example: "admin" │ |
| 87 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 88 | +# │ CACHE_TYPE │ Cache backend type for MCP Gateway │ |
| 89 | +# │ │ Example: "redis" (or "memory" for development) │ |
| 90 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 91 | +# │ HOST │ IP address to bind the service to (required for containers) │ |
| 92 | +# │ │ Must be: "0.0.0.0" (to listen on all interfaces) │ |
| 93 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 94 | +# │ GUNICORN_WORKERS │ Number of Gunicorn worker processes (numeric, no quotes) │ |
| 95 | +# │ │ Example: 1 (keep low for cost efficiency) │ |
| 96 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 97 | +# │ CLOUD_RUN_CPU │ Number of vCPUs allocated (numeric, no quotes) │ |
| 98 | +# │ │ Example: 1 (minimum is 0.08, maximum is 8) │ |
| 99 | +# ├────────────────────────────┼─────────────────────────────────────────────────────────────────┤ |
| 100 | +# │ CLOUD_RUN_MEMORY │ Memory allocation for the service │ |
| 101 | +# │ │ Example: "512Mi" (minimum is 128Mi, maximum is 32Gi) │ |
| 102 | +# └────────────────────────────┴─────────────────────────────────────────────────────────────────┘ |
| 103 | +# |
| 104 | +# Triggers: |
| 105 | +# • Every push to `main` |
| 106 | +# =============================================================== |
| 107 | + |
| 108 | +name: Deploy to Google Cloud Run |
| 109 | + |
| 110 | +on: |
| 111 | + push: |
| 112 | + branches: ["main"] |
| 113 | + |
| 114 | +permissions: |
| 115 | + contents: read |
| 116 | + |
| 117 | +env: |
| 118 | + # ─── project & registry ─────────────────────────── |
| 119 | + GCP_REGION: ${{ vars.GCP_REGION }} |
| 120 | + GAR_REPO_NAME: ${{ vars.GAR_REPO_NAME }} |
| 121 | + IMAGE_NAME: ${{ vars.IMAGE_NAME }} |
| 122 | + IMAGE_TAG: ${{ github.sha }} |
| 123 | + CLOUD_RUN_SERVICE: ${{ vars.CLOUD_RUN_SERVICE }} |
| 124 | + CLOUD_RUN_PORT: ${{ vars.CLOUD_RUN_PORT }} |
| 125 | + |
| 126 | + # ─── app configuration (non-secret) ────────────── |
| 127 | + BASIC_AUTH_USER: ${{ vars.BASIC_AUTH_USER }} |
| 128 | + CACHE_TYPE: ${{ vars.CACHE_TYPE }} |
| 129 | + HOST: ${{ vars.HOST }} |
| 130 | + GUNICORN_WORKERS: ${{ vars.GUNICORN_WORKERS }} |
| 131 | + CLOUD_RUN_CPU: ${{ vars.CLOUD_RUN_CPU }} |
| 132 | + CLOUD_RUN_MEMORY: ${{ vars.CLOUD_RUN_MEMORY }} |
| 133 | + |
| 134 | + # ─── secrets ───────────────────────────────────── |
| 135 | + GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} |
| 136 | + JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} |
| 137 | + BASIC_AUTH_PASSWORD: ${{ secrets.BASIC_AUTH_PASSWORD }} |
| 138 | + DATABASE_URL: ${{ secrets.DATABASE_URL }} |
| 139 | + REDIS_URL: ${{ secrets.REDIS_URL }} |
| 140 | + |
| 141 | + # ─── caching helpers ────────────────────────────── |
| 142 | + CACHE_DIR: /tmp/.buildx-cache |
| 143 | + |
| 144 | +jobs: |
| 145 | + build-push-deploy: |
| 146 | + name: 🚀 Build, Cache, Push & Deploy |
| 147 | + runs-on: ubuntu-latest |
| 148 | + environment: google-cloud-run-production |
| 149 | + # Uses PostgreSQL database and Redis cache |
| 150 | + |
| 151 | + steps: |
| 152 | + # ----------------------------------------------------------- |
| 153 | + # 0️⃣ Checkout repository |
| 154 | + # ----------------------------------------------------------- |
| 155 | + - name: ⬇️ Checkout source |
| 156 | + uses: actions/checkout@v4 |
| 157 | + |
| 158 | + # ----------------------------------------------------------- |
| 159 | + # 1️⃣ Authenticate to Google Cloud |
| 160 | + # ----------------------------------------------------------- |
| 161 | + - name: 🔐 Authenticate to Google Cloud |
| 162 | + uses: google-github-actions/auth@v2 |
| 163 | + with: |
| 164 | + credentials_json: ${{ secrets.GCP_SERVICE_KEY }} |
| 165 | + |
| 166 | + - name: 🧰 Set up gcloud SDK |
| 167 | + uses: google-github-actions/setup-gcloud@v2 |
| 168 | + with: |
| 169 | + project_id: ${{ env.GCP_PROJECT_ID }} |
| 170 | + |
| 171 | + # ----------------------------------------------------------- |
| 172 | + # 2️⃣ Set up Docker Buildx + cache |
| 173 | + # ----------------------------------------------------------- |
| 174 | + - name: 🛠️ Set up Docker Buildx |
| 175 | + uses: docker/setup-buildx-action@v3 |
| 176 | + |
| 177 | + - name: 🔄 Restore BuildKit cache |
| 178 | + uses: actions/cache@v4 |
| 179 | + with: |
| 180 | + path: ${{ env.CACHE_DIR }} |
| 181 | + key: ${{ runner.os }}-buildx-${{ github.sha }} |
| 182 | + restore-keys: ${{ runner.os }}-buildx- |
| 183 | + |
| 184 | + # ----------------------------------------------------------- |
| 185 | + # 3️⃣ Configure Docker to use gcloud auth |
| 186 | + # ----------------------------------------------------------- |
| 187 | + - name: 🔧 Configure Docker for Artifact Registry |
| 188 | + run: gcloud auth configure-docker ${{ env.GCP_REGION }}-docker.pkg.dev |
| 189 | + |
| 190 | + # ----------------------------------------------------------- |
| 191 | + # 4️⃣ Build & tag image |
| 192 | + # ----------------------------------------------------------- |
| 193 | + - name: 🏗️ Build Docker image |
| 194 | + run: | |
| 195 | + docker buildx build \ |
| 196 | + --file Containerfile.lite \ |
| 197 | + --tag ${{ env.GCP_REGION }}-docker.pkg.dev/${{ env.GCP_PROJECT_ID }}/${{ env.GAR_REPO_NAME }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \ |
| 198 | + --cache-from type=local,src=${{ env.CACHE_DIR }} \ |
| 199 | + --cache-to type=local,dest=${{ env.CACHE_DIR }},mode=max \ |
| 200 | + --load \ |
| 201 | + . |
| 202 | + |
| 203 | + # ----------------------------------------------------------- |
| 204 | + # 5️⃣ Push image to Artifact Registry |
| 205 | + # ----------------------------------------------------------- |
| 206 | + - name: 📤 Push image to GAR |
| 207 | + run: | |
| 208 | + docker push ${{ env.GCP_REGION }}-docker.pkg.dev/${{ env.GCP_PROJECT_ID }}/${{ env.GAR_REPO_NAME }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} |
| 209 | + |
| 210 | + # ----------------------------------------------------------- |
| 211 | + # 6️⃣ Deploy to Cloud Run |
| 212 | + # ----------------------------------------------------------- |
| 213 | + - name: 🚀 Deploy to Cloud Run |
| 214 | + run: | |
| 215 | + gcloud run deploy "$CLOUD_RUN_SERVICE" \ |
| 216 | + --image "$GCP_REGION-docker.pkg.dev/$GCP_PROJECT_ID/$GAR_REPO_NAME/$IMAGE_NAME:$IMAGE_TAG" \ |
| 217 | + --region "$GCP_REGION" \ |
| 218 | + --platform managed \ |
| 219 | + --allow-unauthenticated \ |
| 220 | + --port "$CLOUD_RUN_PORT" \ |
| 221 | + --cpu "$CLOUD_RUN_CPU" \ |
| 222 | + --memory "$CLOUD_RUN_MEMORY" \ |
| 223 | + --max-instances 1 \ |
| 224 | + --set-env-vars "JWT_SECRET_KEY=$JWT_SECRET_KEY" \ |
| 225 | + --set-env-vars "BASIC_AUTH_USER=$BASIC_AUTH_USER" \ |
| 226 | + --set-env-vars "BASIC_AUTH_PASSWORD=$BASIC_AUTH_PASSWORD" \ |
| 227 | + --set-env-vars "AUTH_REQUIRED=true" \ |
| 228 | + --set-env-vars "DATABASE_URL=$DATABASE_URL" \ |
| 229 | + --set-env-vars "REDIS_URL=$REDIS_URL" \ |
| 230 | + --set-env-vars "CACHE_TYPE=$CACHE_TYPE" \ |
| 231 | + --set-env-vars "HOST=$HOST" \ |
| 232 | + --set-env-vars "GUNICORN_WORKERS=$GUNICORN_WORKERS" |
| 233 | + |
| 234 | + # ----------------------------------------------------------- |
| 235 | + # 7️⃣ Show deployment status |
| 236 | + # ----------------------------------------------------------- |
| 237 | + - name: 📈 Display deployment status |
| 238 | + run: | |
| 239 | + URL=$(gcloud run services describe "$CLOUD_RUN_SERVICE" --region "$GCP_REGION" --format "value(status.url)") |
| 240 | + echo "🎉 Service deployed to: $URL" |
| 241 | + echo "📊 Service details:" |
| 242 | + gcloud run services describe "$CLOUD_RUN_SERVICE" --region "$GCP_REGION" |
0 commit comments