Deploy to Cloud Run
Index ▾
- 01. Concepts
- 02. Architecture
- 03. State & Storage
- 04. Tools
- 05. BigQuery
- 06. Cloud Storage
- 07. Compute & Containers
- 08. Cloud Run
- 09. Pub/Sub
- 10. Databases
- 11. Observability
- 12. Access & Billing
- 13. Secret Manager
- 14. Networking
- 15. Scheduling & Tasks
- 16. Build & Artifacts
- 17. Guardrails
- 18. Setup
- 19. Deploy to Cloud Run
Deploy to Cloud Run
For anything past a laptop, enoki runs as a Cloud Run service in your own GCP project. Inference uses that project’s Vertex AI as the service account; every data call still runs as the connecting Slack user’s own Google identity. Shared state lives in Firestore so the service can scale to several instances. Nothing is stored outside your project.
Slack ──HTTPS──▶ Cloud Run (gunicorn ▸ app_oauth:flask_app, N instances)
├─ inference ▶ Vertex AI (runtime service account)
├─ GCP tools ▶ Google APIs (each user's OAuth identity)
├─ shared state ▶ Firestore (conversations, pending, idem, audit)
└─ per-user files▶ /app/data (gcsfuse ▸ gs://…-enoki-data) What you need first
- A GCP project with billing enabled and
gcloudauthenticated. - A Slack app (from
manifest.json) — Client ID, Client Secret, Signing Secret, Bot Token. - A Google OAuth 2.0 client (type Web application) in the same project.
- A Fernet key for encrypting stored refresh tokens.
One command
cp deploy/.env.deploy.example deploy/.env.deploy # fill in secrets (gitignored)
bash deploy/deploy.sh The script is idempotent and runs in two phases — the public URL isn’t known until the service exists, so it deploys, reads the URL, sets the OAuth redirect URIs, and redeploys. It will:
- enable the required APIs (Run, Cloud Build, Artifact Registry, Secret Manager, Vertex AI, Storage, Firestore);
- create the Firestore (Native mode) database and the state bucket
gs://<project>-enoki-data; - create a dedicated runtime service account and grant it only
aiplatform.user,datastore.user,secretmanager.secretAccessor(per-secret), andstorage.objectAdminon that one bucket; - push secrets to Secret Manager and deploy Cloud Run gen2 with the bucket mounted at
/app/data,--max-instances=5(override withMAX_INSTANCES),--min-instances=1.
After the first deploy
The script prints the service URL. Finish wiring both providers:
| Provider | Setting | Value |
|---|---|---|
| Slack | OAuth Redirect URL | <URL>/slack/oauth_redirect |
| Slack | Event Subscriptions request URL | <URL>/slack/events |
| Authorized redirect URI | <URL>/google/oauth/callback |
Then install to your workspace at <URL>/slack/install.
Why it scales
Because conversations, pending approvals, idempotency, and the audit log are all Firestore-backed, any instance can handle any request — including an Approve/Deny click
that lands on a different instance than the one that paused the run. That’s what lets --max-instances be greater than one. See State & Storage for
the full model. The runtime service account has no GCP data-plane access — broad reads and
writes are always the user’s own OAuth identity.