textrawl
byJeff Green
Guides

Cloud Run Deployment

Deploy textrawl to Google Cloud Run

Deploy textrawl as a serverless container on Google Cloud Run.

Prerequisites

  • Google Cloud account with billing enabled
  • gcloud CLI installed and authenticated
  • Docker installed
  • Artifact Registry API enabled

Use the provided deployment script:

export GCP_PROJECT_ID=your-project-id
export GCP_REGION=us-central1  # optional, defaults to us-central1
 
./scripts/deploy.sh

Manual Deployment

1. Set Up Artifact Registry

# Enable the API
gcloud services enable artifactregistry.googleapis.com
 
# Create a repository
gcloud artifacts repositories create textrawl \
  --repository-format=docker \
  --location=us-central1
 
# Configure Docker authentication
gcloud auth configure-docker us-central1-docker.pkg.dev

2. Build and Push

# Set variables
PROJECT_ID=your-project-id
REGION=us-central1
IMAGE=$REGION-docker.pkg.dev/$PROJECT_ID/textrawl/textrawl
 
# Build and push
docker build -t $IMAGE:latest .
docker push $IMAGE:latest

3. Deploy

gcloud run deploy textrawl \
  --image $IMAGE:latest \
  --platform managed \
  --region $REGION \
  --allow-unauthenticated \
  --set-secrets="DATABASE_URL=textrawl-database-url:latest,OPENAI_API_KEY=textrawl-openai-key:latest,API_BEARER_TOKEN=textrawl-api-token:latest" \
  --min-instances 0 \
  --max-instances 10 \
  --memory 512Mi \
  --cpu 1 \
  --cpu-boost

Tip: For production, pin secrets to specific versions instead of :latest for stability (e.g., textrawl-api-token:1).

Secret Manager

Store secrets in Google Secret Manager:

# Create secrets (use textrawl- prefix for organization)
echo -n "postgresql://user:pass@ep-xxx-pooler.neon.tech/textrawl?sslmode=require" | gcloud secrets create textrawl-database-url --data-file=-
echo -n "sk-your-openai-key" | gcloud secrets create textrawl-openai-key --data-file=-
echo -n "your-64-char-token" | gcloud secrets create textrawl-api-token --data-file=-

Grant Cloud Run Access to Secrets

Cloud Run needs permission to read each secret. First, get your project number (not project ID):

# Get your project number
gcloud projects describe YOUR_PROJECT_ID --format="value(projectNumber)"

Then grant the default Compute Engine service account access to each secret:

PROJECT_NUMBER=123456789012  # Replace with your project number
 
# Grant access to each secret
for SECRET in textrawl-database-url textrawl-openai-key textrawl-api-token; do
  gcloud secrets add-iam-policy-binding $SECRET \
    --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"
done

Note: PROJECT_NUMBER is a numeric ID (e.g., 123456789012), different from PROJECT_ID (e.g., my-project). You can also find it in the Cloud Console under IAM & Admin > Settings.

Configuration

Cloud Run settings:

  • Memory: 512MB minimum
  • CPU: 1 vCPU
  • Max instances: 10 (adjust for traffic)
  • Min instances: 0 (scale to zero)
  • Timeout: 60s

Large uploads (GCS)

Uploads above MAX_SINGLE_FILE_SIZE_MB use a resumable workflow: the browser PUTs bytes directly to a GCS bucket (never through Cloud Run or Vercel), then the server verifies and processes the object. Storage is selected at runtime — set GCS_UPLOAD_BUCKET to enable GCS; leave it unset to fall back to the in-memory fake (local dev only).

# Point the service at the provisioned bucket
gcloud run services update textrawl --region us-east4 \
  --update-env-vars GCS_UPLOAD_BUCKET=textrawl-uploads
# GCS_PROJECT_ID is optional — auto-detected from the runtime service account.

The bucket itself (creation, IAM, CORS for the dashboard origin, and the abandoned-upload lifecycle rule) is provisioned and version-controlled under infra/gcs/ — see its README.md for the exact commands. Key points:

  • The Cloud Run runtime service account needs roles/storage.objectAdmin on the bucket (granted via ADC — no keys).
  • Bucket CORS must allow the dashboard origin with methods PUT/POST/GET/HEAD and headers Content-Type, Content-Range, x-goog-*.
  • A lifecycle rule deletes abandoned objects after 1 day; soft-delete is disabled so deleted upload bytes are not retained/billed.

Custom Domain

gcloud run domain-mappings create \
  --service textrawl \
  --domain api.yourdomain.com \
  --region us-central1

Monitoring

View metrics in Cloud Console:

  • Request count
  • Latency
  • Error rate
  • Instance count

On this page