Gautam Menon
07/23/2025, 10:02 AMMarvin
07/23/2025, 10:02 AMMarvin
07/23/2025, 10:03 AMpython
from prefect import flow, task
from datetime import timedelta
@flow(flow_run_name="my-flow", timeout_seconds=3600)
def my_flow():
# This flow will automatically fail if it can't communicate
# with the server for more than an hour
...
2. Use Task Retries Independent of Server:
python
from prefect import task
from datetime import timedelta
@task(retries=3, retry_delay_seconds=60)
def my_task():
# This task will retry locally even if server is down
...
3. Consider implementing your own monitoring solution to detect and handle zombie flows:
python
from prefect.client import get_client
from datetime import datetime, timedelta
async def cleanup_zombie_flows():
client = get_client()
flows = await client.read_flow_runs(
flow_run_filter={"state": {"type": "RUNNING"}},
)
# Handle flows that have been running too long
...
For more details, you can refer to:
- Detecting Zombie Flows Documentation
- Task Retries Documentation
Remember that while Prefect provides mechanisms to handle server failures gracefully, it's important to:
1. Have proper monitoring in place to detect server issues quickly
2. Implement appropriate retry and timeout mechanisms at both task and flow levels
3. Have a disaster recovery plan for your self-hosted server
4. Consider using backup servers or failover mechanisms for critical workflowsGautam Menon
11/11/2025, 8:15 AMMarvin
11/11/2025, 8:15 AMMarvin
11/11/2025, 8:25 AMpip install -U prefect-gcp
2) Create a Google Cloud Run (push) work pool.
- Easiest: in Prefect UI > Infrastructure > Work pools > New > choose Google Cloud Run (push).
- CLI alternative (type string varies by version; ensure prefect-gcp is installed):
prefect work-pool create my-cr-push --type cloud-run:push
If the type isn’t recognized, ensure prefect-gcp is installed and use the UI to pick the exact type name.
3) Configure the work pool variables
- Project ID, region
- Runtime service account email (the one your Cloud Run Jobs will run as)
- Image, CPU/memory, max retries, timeout, env vars, VPC connector if needed
- Credentials for Prefect to call the Cloud Run API:
- Provide a GCP service account key (JSON) with permission to create/run Cloud Run Jobs. See IAM below.
4) IAM for the service accounts
- “Provisioning” SA (used by Prefect to create jobs):
- roles/run.admin (or a minimal set that includes run.jobs.create/run.jobs.get/run.jobs.run)
- roles/iam.serviceAccountUser on the runtime service account
- “Runtime” SA (used by the Cloud Run Job containers):
- roles/artifactregistry.reader (to pull your image)
- Any resource access your flow needs (GCS/BigQuery/etc.)
- Cloud Logging is typically handled automatically by the platform
5) Deploy your flow from source to the work pool
Example:
from prefect import flow
from prefect.deployments import DeploymentImage
@flow(log_prints=True)
def my_flow():
print("Hello from Cloud Run!")
# Deploy from a repo; Prefect will use your pool's template to run as a Cloud Run Job
flow.from_source(
"<https://github.com/your-org/your-repo>",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="cloud-run-job",
work_pool_name="my-cr-push",
image=DeploymentImage(
name="<region>-docker.pkg.dev/<project>/<repo>/myflow:latest",
platform="linux/amd64", # match your build
),
)
Links
- GCP worker guide
- Workers and work poolsMarvin
11/11/2025, 8:25 AMpip install -U prefect-gcp
2) Create a Cloud Run V2 (pull) work pool
- UI: Infrastructure > Work pools > New > choose Google Cloud Run V2 (pull)
- CLI (ensure prefect-gcp is installed; type can vary by version):
prefect work-pool create my-cr-pull --type cloud-run-v2
3) Build a worker image that runs both a tiny HTTP server and the Prefect worker
app.py:
import os
import threading
import http.server
import socketserver
import subprocess
def serve():
port = int(os.environ.get("PORT", "8080"))
handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", port), handler) as httpd:
httpd.serve_forever()
def run_worker():
pool = os.environ["PREFECT_WORK_POOL"]
name = os.environ.get("PREFECT_WORKER_NAME", "cr-worker")
subprocess.call(["prefect", "worker", "start", "--pool", pool, "--name", name])
if __name__ == "__main__":
threading.Thread(target=serve, daemon=True).start()
run_worker()
Dockerfile:
FROM <http://ghcr.io/prefecthq/prefect:3-latest|ghcr.io/prefecthq/prefect:3-latest>
RUN pip install "prefect-gcp>=0.6"
ENV PREFECT_LOGGING_LEVEL=INFO
# The app both serves HTTP and runs the worker loop
COPY app.py /app/app.py
CMD ["python", "/app/app.py"]
4) Deploy the worker as a Cloud Run Service
- Set env vars:
- PREFECT_API_URL (to Prefect Cloud or your Prefect server)
- PREFECT_API_KEY (if using Prefect Cloud)
- PREFECT_WORK_POOL=my-cr-pull
- PREFECT_WORKER_NAME=cr-worker-1 (optional)
- Configure:
- Min instances >= 1 (to keep “warm”)
- CPU Always Allocated = on (so it can work outside request)
- Appropriate memory/CPU
- Service account for the worker with permissions below
5) IAM
- Worker Service Account:
- roles/run.admin (or minimally run.jobs.create/run.jobs.get/run.jobs.run)
- roles/iam.serviceAccountUser on the runtime SA used by the jobs
- Cloud Run Job runtime SA:
- roles/artifactregistry.reader
- Resource roles your flow needs (GCS/BigQuery/etc.)
6) Deploy flows to the pull pool
Use the same deployment pattern as above, but target work_pool_name="my-cr-pull".
About “returning output”
- Prefect captures logs and states for every run. To persist data/results, use results or artifacts in your flow:
from prefect import flow, artifacts
@flow
def my_flow():
result = {"status": "ok", "rows": 42}
artifacts.create_markdown("summary", markdown=f"Rows processed: {result['rows']}")
return result
- You can also write outputs to GCS/BigQuery and link them via artifacts.
Recommendations and next steps
- If your goal is just “keep something listening so runs start immediately,” a Cloud Run-based pull worker can be kept warm — but it won’t remove Cloud Run Job cold starts. For true warm executors, consider GKE with the Kubernetes worker.
- If simplicity and reliability are priorities, use the Cloud Run push work pool — it’s designed for Cloud Run and removes the need to host a long-running worker.
A few clarifying questions to make sure we point you to the best setup:
- Are you using Prefect Cloud or a self-hosted Prefect server? (We’ll tailor API auth accordingly.)
- Is the “warm containers” requirement to minimize Cloud Run Job cold start times, or mainly to keep a listener available?
- Do your flows need VPC access or private Artifact Registry access?Marvin
11/11/2025, 8:25 AMGautam Menon
11/11/2025, 2:25 PMMarvin
11/11/2025, 2:25 PMMarvin
11/11/2025, 2:26 PMprefect work-pool create my-cr-pull --type cloud-run-v2
Configure pool variables (in the UI)
- Project ID and region
- Runtime service account email (this is the SA your Cloud Run Jobs will run as)
- Default Job settings: image, CPU/memory, timeout, retries, env vars, VPC connector (if your Prefect Server is only reachable privately)
- Optionally leave the image blank in the pool and set it per-deployment
B) Run a Prefect worker as a Cloud Run Service (kept warm)
- Build a lightweight image that includes Prefect + prefect-gcp and runs the worker. Because Cloud Run Services need to serve HTTP, run a tiny HTTP server alongside the worker and enable CPU always allocated so the background loop is active.
app.py:
import os
import threading
import http.server
import socketserver
import subprocess
def serve():
port = int(os.environ.get("PORT", "8080"))
handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", port), handler) as httpd:
httpd.serve_forever()
def run_worker():
pool = os.environ["PREFECT_WORK_POOL"]
name = os.environ.get("PREFECT_WORKER_NAME", "cr-worker")
subprocess.call(["prefect", "worker", "start", "--pool", pool, "--name", name])
if __name__ == "__main__":
threading.Thread(target=serve, daemon=True).start()
run_worker()
Dockerfile:
FROM <http://ghcr.io/prefecthq/prefect:3-latest|ghcr.io/prefecthq/prefect:3-latest>
RUN pip install "prefect-gcp>=0.6"
ENV PREFECT_LOGGING_LEVEL=INFO
COPY app.py /app/app.py
CMD ["python", "/app/app.py"]
Deploy the worker as a Cloud Run Service:
- Attach a service account with the IAM listed below
- Set min instances >= 1 and CPU always allocated (via “no CPU throttling”)
- Set env vars:
- PREFECT_API_URL (e.g., http://YOUR_SERVER_HOST:4200/api)
- PREFECT_WORK_POOL=my-cr-pull
- PREFECT_WORKER_NAME=cr-worker-1 (optional)
- If your self-hosted server requires auth, set PREFECT_API_KEY as well
Example gcloud (adjust region/flags to your org’s policy):
gcloud run deploy prefect-cr-worker \
--image=REGION-docker.pkg.dev/PROJECT/repo/prefect-cr-worker:latest \
--region=REGION \
--service-account=WORKER_SA@PROJECT.iam.gserviceaccount.com \
--set-env-vars=PREFECT_API_URL=<http://YOUR_SERVER_HOST:4200/api,PREFECT_WORK_POOL=my-cr-pull> \
--min-instances=1 \
--no-cpu-throttling \
--execution-environment=gen2 \
--memory=1Gi --cpu=1
C) IAM you’ll typically need
- Worker Service Account (attached to the Cloud Run Service):
- roles/run.admin (or minimally: permissions to create/run/get Cloud Run Jobs and Executions)
- roles/iam.serviceAccountUser on the runtime SA used by the Job
- Runtime Service Account (used by the Cloud Run Job containers):
- roles/artifactregistry.reader (to pull your pre-built image)
- roles/logging.logWriter (commonly granted by default for Cloud Run)
- Any data access your flows need (e.g., GCS/BigQuery/etc.)
D) Deploy flows that use your pre-built imageMarvin
11/11/2025, 2:26 PMfrom prefect import flow
from prefect.deployments import DeploymentImage
@flow
def my_flow():
...
flow.from_source(
"<https://github.com/your-org/your-repo>",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="cr-job",
work_pool_name="my-cr-pull",
image=DeploymentImage(
name="REGION-docker.pkg.dev/PROJECT/repo/your-flow-image:stable",
platform="linux/amd64",
),
)
E) Networking to your self-hosted server
- If your Prefect Server is publicly reachable, ensure the firewall allows egress from Cloud Run egress IP ranges.
- If it’s private-only, configure a VPC connector on both the worker Service and the Cloud Run Jobs, and ensure routing to your VM’s internal address is allowed.
F) Minimizing Cloud Run Job cold start (within Cloud Run constraints)
- Keep images small and in the same region as your Jobs (Artifact Registry and Job region should match).
- Use slim/distroless bases and avoid large, on-start downloads.
- Pre-bundle dependencies; avoid pip install at runtime.
- Prefer gen2 execution environment for better startup performance.
- Set a small initial CPU/memory that still supports fast start (too little memory can slow JIT/imports).
- If you invoke many different images, consider consolidating to fewer shared base layers to improve caching in GCP infra.
If truly warm executors are required
- Consider the Kubernetes work pool on GKE: you can maintain a small pool of nodes and pre-warm Pods, which gives near-instant starts for new runs.
- Alternatively, run flows on GCE VMs with the Process or Docker worker to avoid container startup overhead.
References
- GCP worker guide
- Workers and work pools
- prefect-gcp integration
Quick confirmation questions so I can tailor exact commands/policies:
- Is your Prefect Server reachable publicly or only via private VPC?
- Do you want to stick with Cloud Run Jobs (per-run images) or would you entertain GKE for true warm executors?Gautam Menon
11/11/2025, 2:32 PMMarvin
11/11/2025, 2:33 PMMarvin
11/11/2025, 2:36 PM