Michael
09/29/2025, 6:44 AM"job_configuration": {
"env": "{{ env }}",
"name": "{{ name }}",
"labels": "{{ labels }}",
"region": "{{ region }}",
"command": "{{ command }}",
"timeout": "{{ timeout }}",
"job_body": {
"kind": "Job",
"spec": {
"template": {
"spec": {
"template": {
"spec": {
"containers": [
{
"image": "{{ image }}",
"command": "{{ command }}",
"resources": {
"limits": {
"cpu": "{{ cpu }}",
"memory": "{{ memory }}"
},
"requests": {
"cpu": "{{ cpu }}",
"memory": "{{ memory }}"
}
}
}
],
"timeoutSeconds": "{{ timeout }}",
"serviceAccountName": "{{ service_account_name }}"
}
}
},
"metadata": {
"name": "{{ name }}",
"annotations": {
"<http://run.googleapis.com/cloudsql-instances|run.googleapis.com/cloudsql-instances>": "{{ cloudsql_instance }}"
}
}
}
},
"metadata": {
"name": "{{ name }}",
"annotations": {
"<http://run.googleapis.com/launch-stage|run.googleapis.com/launch-stage>": "BETA",
"<http://run.googleapis.com/vpc-access-connector|run.googleapis.com/vpc-access-connector>": "{{ vpc_connector_name }}"
}
},
"apiVersion": "<http://run.googleapis.com/v1|run.googleapis.com/v1>"
}
Marvin
09/29/2025, 6:44 AMMarvin
09/29/2025, 6:49 AM"job_configuration": {
"env": {{ env }},
"name": "{{ name }}",
"labels": {{ labels }},
"region": "{{ region }}",
"command": {{ command }},
"timeout": "{{ timeout }}",
"job_body": {
"apiVersion": "<http://run.googleapis.com/v1|run.googleapis.com/v1>",
"kind": "Job",
"metadata": {
"name": "{{ name }}",
"annotations": {
"<http://run.googleapis.com/launch-stage|run.googleapis.com/launch-stage>": "BETA",
"<http://run.googleapis.com/vpc-access-connector|run.googleapis.com/vpc-access-connector>": "{{ vpc_connector_name }}"
},
"labels": {{ labels }}
},
"template": {
"metadata": {
"annotations": {
"<http://run.googleapis.com/cloudsql-instances|run.googleapis.com/cloudsql-instances>": "{{ cloudsql_instance }}"
}
},
"template": {
"containers": [
{
"image": "{{ image }}",
"command": {{ command }},
"env": {{ env }},
"resources": {
"limits": {
"cpu": "{{ cpu }}",
"memory": "{{ memory }}"
}
}
}
],
"serviceAccount": "{{ service_account_name }}",
"timeout": "{{ timeout }}"
}
}
}
}
Notes:
- If you need requests as well as limits, you can include them under resources, but Cloud Run v2 primarily respects limits.
- timeout should be a duration string in the job template (e.g., "600s"). The top-level job_configuration.timeout can be your templated value that resolves to a duration string.
- Keep the command as a list of strings, e.g., ["python", "-m", "prefect.engine"].
Docs for reference:
- Prefect GCP worker guide
- Workers concepts
If this still errors, can you share:
- Your versions of prefect and prefect-gcp (pip show prefect prefect-gcp
)
- The rendered job_body (with secrets redacted), to confirm types and keys after templating?Michael
09/29/2025, 7:13 AMMarvin
09/29/2025, 7:15 AMMarvin
09/29/2025, 7:16 AMtemplate
field in job_body
, but your payload is using a Kubernetes/Cloud Run v1-style spec
nesting. The validator is literally looking for /template
at the root of job_body
and can’t find it.
Additional issues I see:
- You’re using v1 keys: serviceAccountName
and timeoutSeconds
. For Cloud Run v2, use serviceAccount
and timeout
(duration string like "600s").
- Slack auto-links in your annotation keys will break validation. Use plain keys like <http://run.googleapis.com/vpc-access-connector|run.googleapis.com/vpc-access-connector>
(no <...|...>
).
- Don’t quote non-string placeholders such as env
, labels
, and command
— quoting will turn them into strings instead of dicts/lists.
Here’s a job_configuration that fits Cloud Run v2 and Prefect’s CloudRunWorkerJobV2Configuration
expectations. Note the top-level template
and the v2 keys:
"job_configuration": {
"env": {{ env }},
"name": "{{ name }}",
"labels": {{ labels }},
"region": "{{ region }}",
"command": {{ command }},
"timeout": "{{ timeout }}",
"job_body": {
"kind": "Job",
"metadata": {
"name": "{{ name }}",
"labels": {{ labels }},
"annotations": {
"<http://run.googleapis.com/launch-stage|run.googleapis.com/launch-stage>": "BETA"
}
},
"template": {
"metadata": {
"annotations": {
"<http://run.googleapis.com/cloudsql-instances|run.googleapis.com/cloudsql-instances>": "{{ cloudsql_instance }}",
"<http://run.googleapis.com/vpc-access-connector|run.googleapis.com/vpc-access-connector>": "{{ vpc_connector_name }}"
}
},
"template": {
"containers": [
{
"image": "{{ image }}",
"command": {{ command }},
"env": {{ env }},
"resources": {
"limits": {
"cpu": "{{ cpu }}",
"memory": "{{ memory }}"
}
}
}
],
"serviceAccount": "{{ service_account_name }}",
"timeout": "{{ timeout }}"
}
}
}
}
Key differences from your current payload:
- job_body.template
exists at the top level (not under spec
).
- Uses template.template.containers
for the container spec.
- Uses serviceAccount
and timeout
(duration, e.g., "600s") instead of serviceAccountName
and timeoutSeconds
.
- Removes Slack link formatting from annotation keys.
- Removes quotes around env
, labels
, and command
so they render as dicts/lists.
If you still see the same error after this change, please share:
- Your versions of prefect
and prefect-gcp
- The rendered job_body
(after templating, with secrets redacted) so we can confirm the final structure and types.
Reference:
- Prefect GCP worker guide
- Workers concepts