Ritesh
07/24/2025, 11:59 AMMarvin
07/24/2025, 11:59 AMMarvin
07/24/2025, 12:00 PMpython
from prefect.infrastructure.kubernetes import KubernetesJob
kubernetes_job = KubernetesJob(
job_configuration={
"spec": {
"template": {
"spec": {
# Option 1: Using nodeSelector
"nodeSelector": {
"node-group": "your-node-group-label" # Use your actual node group label
},
# Option 2: Using node affinity (more flexible)
"affinity": {
"nodeAffinity": {
"requiredDuringSchedulingIgnoredDuringExecution": {
"nodeSelectorTerms": [{
"matchExpressions": [{
"key": "node-group",
"operator": "In",
"values": ["your-node-group-name"]
}]
}]
}
}
}
}
}
}
}
)
2. Using Karpenter Provisioner Configuration
You can also use Karpenter's provisioner configuration to ensure pods land on specific node groups by using:
- Node selectors
- Taints and tolerations
- Resource requirements
- Labels and annotations
For example, in your flow deployment:
python
from prefect import flow
from prefect.deployments import Deployment
from prefect.infrastructure.kubernetes import KubernetesJob
@flow
def my_flow():
# your flow code here
pass
kubernetes_job = KubernetesJob(
job_configuration={
"spec": {
"template": {
"metadata": {
"labels": {
"app": "my-flow",
"node-group": "specific-group"
}
},
"spec": {
"tolerations": [{
"key": "dedicated",
"value": "ml-workload",
"effect": "NoSchedule"
}],
"nodeSelector": {
"node-group": "ml-nodes" # Your specific node group
}
}
}
}
}
)
deployment = Deployment.build_from_flow(
flow=my_flow,
name="my-flow-deployment",
infrastructure=kubernetes_job
)
Important considerations:
1. Make sure your node groups are properly labeled in EKS
2. Verify that Karpenter's provisioner configuration allows scheduling on your target node groups
3. Consider using node affinity over nodeSelector if you need more flexible scheduling rules
4. Test with a small flow first to verify the configuration works as expected
To verify which nodes your flows are running on, you can check:
bash
kubectl get pods -n your-namespace -o wide
Remember that using node selectors or affinity rules might impact the scheduling flexibility and could potentially cause pods to remain pending if the specified nodes are not available. Make sure to align this with your Karpenter scaling policies.
Would you like me to provide more specific details about any of these approaches or help with a particular configuration?Ritesh
07/28/2025, 7:44 AMMarvin
07/28/2025, 7:45 AMMarvin
07/28/2025, 7:46 AMbash
prefect work-pool create "my-k8s-pool" --type kubernetes
- You can then customize the base job template to include resource requirements like:
json
{
"spec": {
"template": {
"spec": {
"containers": [{
"resources": {
"requests": {
"memory": "512Mi",
"cpu": "500m"
},
"limits": {
"memory": "1Gi",
"cpu": "1"
}
}
}]
}
}
}
}
b) KubernetesClusterConfig Block:
- You can configure additional Kubernetes-specific settings using a KubernetesClusterConfig block
3. Important Notes:
- Resource allocation is handled by Kubernetes, not Prefect
- You'll need to ensure your EKS cluster has sufficient resources to meet the requirements
- The requirements are specified at the work pool level, not at individual task level
- The configuration follows standard Kubernetes resource specification format
4. Best Practices:
- Start with reasonable resource requests based on your workload
- Monitor actual resource usage to optimize the settings
- Consider using different work pools for tasks with significantly different resource needs
For more detailed information, you can refer to the Prefect Kubernetes documentation.
Unfortunately, Prefect 3.x doesn't currently support specifying resource requirements at the individual task level - it's managed at the work pool level through Kubernetes configurations. If you need different resource requirements for different tasks, you would need to create separate work pools with different resource configurations.
Would you like me to provide more specific examples of how to set up work pools with different resource requirements, or would you like to know more about any particular aspect of this setup?Ritesh
07/28/2025, 7:49 AMMarvin
07/28/2025, 7:50 AMMarvin
07/28/2025, 7:51 AMpython
from prefect import flow
from prefect.deployments import run_deployment
from prefect.events import Event
@flow(name="high_resource_flow")
def flow1():
# Your high-resource computation here
pass
@flow(name="low_resource_flow")
def flow2():
# Your low-resource computation here
pass
# Deploy both flows to different work pools based on resource requirements
flow1_deployment = flow1.from_source(
source="your_repo",
entrypoint="path/to/file.py:flow1"
).deploy(
name="high-resource-deployment",
work_pool_name="high-resource-pool" # Work pool configured with higher resources
)
flow2_deployment = flow2.from_source(
source="your_repo",
entrypoint="path/to/file.py:flow2"
).deploy(
name="low-resource-deployment",
work_pool_name="low-resource-pool" # Work pool configured with lower resources
)
@flow
def orchestrator_flow():
# Run flow1 and wait for completion
flow1_run = run_deployment(
name="high-resource-deployment/flow1",
timeout=3600 # Adjust timeout as needed
)
# After flow1 completes, run flow2
if flow1_run.state.is_completed():
flow2_run = run_deployment(
name="low-resource-deployment/flow2",
timeout=3600
)
2. Using Event-Based Triggers:
python
from prefect.deployments import DeploymentEventTrigger
from prefect.server.schemas.triggers import DeploymentTriggerTypes
# Create a trigger for flow2 that depends on flow1's completion
trigger = DeploymentEventTrigger(
name="flow2_trigger",
deployment_name="low-resource-deployment/flow2", # Target deployment to run
event_type="prefect.flow-run.Completed", # Trigger on completion
source_deployment_name="high-resource-deployment/flow1" # Source deployment to watch
)
To set this up:
1. Create Two Work Pools with different resource configurations:
bash
# Create work pool for high-resource tasks
prefect work-pool create "high-resource-pool" --type kubernetes
# Create work pool for low-resource tasks
prefect work-pool create "low-resource-pool" --type kubernetes
2. Configure Resource Requirements in each work pool's base job template:
For high-resource-pool:
json
{
"spec": {
"template": {
"spec": {
"containers": [{
"resources": {
"requests": {
"memory": "8Gi",
"cpu": "2"
},
"limits": {
"memory": "16Gi",
"cpu": "4"
}
}
}]
}
}
}
}
For low-resource-pool:
json
{
"spec": {
"template": {
"spec": {
"containers": [{
"resources": {
"requests": {
"memory": "512Mi",
"cpu": "500m"
},
"limits": {
"memory": "1Gi",
"cpu": "1"
}
}
}]
}
}
}
}
3. Deploy Your Flows to their respective work pools and set up the trigger.
Key Benefits:
- Each flow runs with appropriate resources
- Automatic handling of dependencies
- Robust error handling and monitoring
- Scalable and maintainable approach
Important Notes:
- Make sure to have workers running for both work pools
- Monitor the first few runs to ensure resource allocations are appropriate
- Consider adding error handling and retry logic
- You can add scheduling to the orchestrator flow if you need periodic execution
Would you like me to elaborate on any part of this setup or provide more details about specific aspects like error handling or scheduling?Ritesh
08/06/2025, 8:19 AMMarvin
08/06/2025, 8:19 AMMarvin
08/06/2025, 8:20 AMbash
prefect work-pool create "k8s-specific-nodes" --type kubernetes
2. Configure Node Selection
There are two main ways to configure node selection:
a. Using Node Selectors
yaml
# job-template.yaml
job_configuration:
spec:
template:
spec:
nodeSelector:
node-group: your-node-group-label # Replace with your actual node group label
b. Using Node Affinity
yaml
# job-template.yaml
job_configuration:
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-group
operator: In
values:
- your-node-group-name
3. Apply the Configuration
Update your work pool with the custom job template:
bash
prefect work-pool create "k8s-specific-nodes" --base-job-template job-template.yaml --overwrite
4. Deploy Your Flow
When deploying your flow, specify the work pool:
python
from prefect import flow
@flow
def my_flow():
# your flow code here
pass
if __name__ == "__main__":
my_flow.deploy(
name="my-deployment",
work_pool_name="k8s-specific-nodes"
)
Important Notes:
- Make sure your node groups are properly labeled in your Kubernetes cluster
- The node selector labels must match exactly what's configured in your Kubernetes cluster
- You can verify the node labels in your cluster using: kubectl get nodes --show-labels
- Consider using node affinity rules when you need more complex node selection logic
- Resource requests and limits in the job template can also influence node selection if you have nodes with different resource capabilities
For more detailed information, you can refer to the Prefect Kubernetes documentation.
Would you like me to provide more specific examples for your particular use case or explain any part of this in more detail?Ritesh
08/06/2025, 1:38 PMMarvin
08/06/2025, 1:38 PMMarvin
08/06/2025, 1:39 PMRitesh
08/06/2025, 1:40 PMMarvin
08/06/2025, 1:40 PMMarvin
08/06/2025, 1:40 PMMarvin
08/06/2025, 1:40 PMdockerfile
FROM python:3.9
RUN pip install prefect
# Add any other dependencies you need
COPY your_flows /flows
2. Create Kubernetes Manifests:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prefect-worker
spec:
template:
spec:
containers:
- name: worker
image: your-prefect-image:tag
command: ["prefect", "worker", "start", "--pool", "your-pool"]
3. Deploy Required RBAC:
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: prefect-worker
---
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: Role
metadata:
name: prefect-worker
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["create", "get", "list", "watch", "delete"]
However, I recommend using the Helm chart because:
1. It handles all the RBAC setup automatically
2. Includes best practices for Kubernetes deployments
3. Makes upgrades and maintenance easier
4. Provides better default configurations
5. Includes monitoring and logging setup
If you still want to use regular Prefect, you'll need to:
1. Handle your own Kubernetes configurations
2. Set up proper RBAC permissions
3. Configure networking and service discovery
4. Manage secrets and configurations manually
Would you like me to provide more specific details about either approach? Or would you like to know more about a particular aspect of running Prefect on Kubernetes?Marvin
08/06/2025, 1:41 PMyaml
# Example Kubernetes deployment manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: prefect-worker
spec:
template:
spec:
containers:
- name: prefect-worker
image: prefecthq/prefect:2-python3.9
command: ["prefect", "worker", "start", "--pool", "your-work-pool"]
env:
- name: PREFECT_API_KEY
valueFrom:
secretKeyRef:
name: prefect-api-key
key: key
- name: PREFECT_API_URL
value: "<https://api.prefect.cloud/api/accounts/[YOUR-ACCOUNT-ID]/workspaces/[YOUR-WORKSPACE-ID]>"
Challenges with Manual K8s Deployment:
1. Configuration Complexity:
- You need to manually create all Kubernetes resources
- Must handle RBAC permissions yourself
- Need to manage secrets and configurations
- Must set up proper service accounts
2. Missing Automation:
- No automatic handling of work pool configuration
- Manual setup of monitoring and logging
- More complex upgrade process
- Need to handle container lifecycle manually
3. Maintenance Overhead:
- Updates must be managed manually
- More difficult to troubleshoot issues
- No standardized deployment structure
- Higher risk of misconfiguration
Recommendation:
While it's technically possible to run Prefect on Kubernetes without Helm, I strongly recommend using the Helm chart because:
1. It's the officially supported method
2. Provides consistent deployment structure
3. Handles complex configurations automatically
4. Makes upgrades and maintenance easier
5. Includes best practices by default
If you have a specific reason for wanting to avoid Helm, I'd be happy to understand your use case better and provide more targeted guidance. Are there particular concerns about using Helm that you'd like to discuss?Ritesh
08/08/2025, 8:02 AMMarvin
08/08/2025, 8:02 AMMarvin
08/08/2025, 8:14 AMMarvin
08/08/2025, 8:14 AMRitesh
08/12/2025, 7:09 AMMarvin
08/12/2025, 7:09 AMMarvin
08/12/2025, 7:12 AMjob_variables
.
Two common ways:
1) Set resources at the work pool level (global default)
- Fetch the default Kubernetes base job template:
prefect work-pool get-default-base-job-template --type kubernetes -f k8s_base_job_template.json
- Edit k8s_base_job_template.json
and add resources
to the flow container. For example (showing just the relevant part under spec.template.spec.containers[0]):
{
"...": "...",
"job_configuration": {
"job": {
"apiVersion": "batch/v1",
"kind": "Job",
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "flow",
"image": "{{ image }}",
"args": [...],
"resources": {
"limits": { "cpu": "1000m", "memory": "1Gi" },
"requests": { "cpu": "500m", "memory": "512Mi" }
}
}
],
"restartPolicy": "Never"
}
}
}
}
}
}
- Apply it when creating or updating the work pool:
# create (or update with --overwrite) a pool using this template
prefect work-pool create my-k8s-pool --type kubernetes --base-job-template k8s_base_job_template.json
# or update an existing pool
prefect work-pool update my-k8s-pool --base-job-template k8s_base_job_template.json
2) Allow per-deployment overrides via job_variables
- Expose a variable (e.g., resources
) in the base job template and template it into the container spec. Simplified example:
{
"variables": {
"image": {"type": "string"},
"resources": {"type": "object", "default": {}}
},
"job_configuration": {
"job": {
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "flow",
"image": "{{ image }}",
"resources": {{ resources | tojson }}
}
]
}
}
}
}
}
}
- Then set it at deploy time:
Python:
from prefect import flow
@flow
def my_flow():
...
my_flow.from_source("<https://github.com/your/repo>").deploy(
name="k8s-deploy",
work_pool_name="my-k8s-pool",
job_variables={
"resources": {
"limits": {"cpu": "2000m", "memory": "2Gi"},
"requests": {"cpu": "1000m", "memory": "1Gi"}
}
}
)
prefect.yaml (for prefect deploy
):
deployments:
- name: k8s-deploy
entrypoint: flows/my_flow.py:my_flow
work_pool:
name: my-k8s-pool
job_variables:
resources:
limits:
cpu: "2000m"
memory: "2Gi"
requests:
cpu: "1000m"
memory: "1Gi"
Notes
- The resources must be placed on the container that runs your flow (usually the first container, often named flow
in the default template).
- Out of the box, the default template may not expose resources
as a variable. You can still hardcode them in the template or add a variable as shown above to allow per-deployment overrides.
- Prefect 3.x removed Deployment.build_from_flow()
and prefect deployment build
. Use flow.from_source(...).deploy(...)
or prefect deploy
.
Quick sanity check: are you using a Prefect 3 Kubernetes work pool/worker? And do you want these limits set globally for the pool or vary by deployment?Ritesh
08/12/2025, 7:14 AMMarvin
08/12/2025, 7:14 AMMarvin
08/12/2025, 7:15 AMprefect[kubernetes]
is a Python extra. It installs Prefect plus the Kubernetes client libs so your code and workers can talk to a cluster. It doesn’t create any Kubernetes resources by itself.
- The Prefect Helm chart deploys things into your cluster: in 3.x that typically means a Kubernetes Worker (to launch your flow runs as Jobs/Pods) and, if you’re self-hosting, Prefect Server (API/UI/DB). If you use Prefect Cloud, you generally install just the worker via Helm and point it at your Cloud workspace.
Common workflows:
- Local/dev machine: pip install "prefect[kubernetes]"
so you can create a K8s work pool, manage base job templates, and develop flows.
- Cluster: install the Helm chart to run a long-lived Kubernetes worker (and optionally Server if you’re self-hosting).
Example pointers:
- Prefect Helm chart: prefect-helm
- Prefect docs (Kubernetes): https://docs.prefect.io/latest/
If you tell me whether you’re on Prefect Cloud or self-hosting, I can give you a minimal Helm values snippet for “worker only” vs “full server + worker.”Ritesh
08/12/2025, 7:29 AMMarvin
08/12/2025, 7:29 AMMarvin
08/12/2025, 7:31 AMprefect work-pool get-default-base-job-template --type kubernetes -f k8s_base.json
# edit k8s_base.json and add metadata.namespace (and optionally a variable)
Example (snippet):
{
"variables": {
"image": {
"type": "string"
},
"namespace": {
"type": "string",
"default": "team-a"
}
},
"job_configuration": {
"job": {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"namespace": "{{ namespace }}"
},
"spec": {
"template": {
"spec": {
"containers": [
{
"name": "flow",
"image": "{{ image }}"
}
],
"restartPolicy": "Never"
}
}
}
}
}
}
Apply it:
prefect work-pool update my-k8s-pool --base-job-template k8s_base.json
3) Per-deployment override via job_variables
- If your base job template exposes a namespace
variable (as above), you can set it per deployment:
Python:
from prefect import flow
@flow
def my_flow(): ...
my_flow.from_source("<https://github.com/you/repo>").deploy(
name="k8s-deploy",
work_pool_name="my-k8s-pool",
job_variables={"namespace": "team-b"}
)
prefect.yaml (for prefect deploy
):
deployments:
- name: k8s-deploy
entrypoint: flows/my_flow.py:my_flow
work_pool:
name: my-k8s-pool
job_variables:
namespace: team-b
Helm-installed worker
- If you deployed the worker via Helm, set the namespace the same way: put metadata.namespace
(and an optional namespace
variable) in the worker’s baseJobTemplate values so all Jobs land there by default.
Example values.yaml fragment:
worker:
enabled: true
workPool:
name: my-k8s-pool
type: kubernetes
baseJobTemplate:
variables:
namespace:
type: string
default: team-a
job_configuration:
job:
apiVersion: batch/v1
kind: Job
metadata:
namespace: "{{ namespace }}"
spec:
template:
spec:
serviceAccountName: prefect-worker
containers:
- name: flow
image: "{{ image }}"
restartPolicy: Never
Helm chart: prefect-helm
Notes
- Ensure the worker’s service account has permissions to create Jobs/Pods in the target namespace.
- If you don’t include metadata.namespace
, the worker typically submits to its own namespace.
Do you want all runs in a single namespace or different ones per deployment/team? I can tailor the template accordingly.Ritesh
08/12/2025, 11:14 AMMarvin
08/12/2025, 11:14 AMexplore_module_offerings
📊 Tools used:
• check_cli_command
(6x)
• explore_module_offerings
(4x)Marvin
08/12/2025, 11:15 AMMarvin
08/12/2025, 11:15 AMjob_variables
aren’t touching the actual pod spec.
Why it didn’t work:
- The Kubernetes worker’s default base job template uses job_manifest
(and specific Jinja variables like image
, env
, namespace
, etc.). There is no job_spec
variable, so job_variables.job_spec
is ignored.
- Slack auto-linking mangled your label key: it must be exactly <http://node.kubernetes.io/instance-type|node.kubernetes.io/instance-type>
(no URLs, no pipes). Also, keys with dots should be quoted in YAML.
How to fix it
Option A — set it at the work pool level (hardcode in the base job template)
1) Export the default template:
prefect work-pool get-default-base-job-template --type kubernetes -f base.json
2) Edit base.json
to include your nodeSelector and resources under job_configuration.job_manifest.spec.template.spec
and the container:
{
"job_configuration": {
"job_manifest": {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"generateName": "{{ name }}-",
"namespace": "{{ namespace }}"
},
"spec": {
"template": {
"spec": {
"nodeSelector": {
"<http://node.kubernetes.io/instance-type|node.kubernetes.io/instance-type>": "g4dn.xlarge"
},
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"args": "{{ command }}",
"env": "{{ env }}",
"resources": {
"requests": { "cpu": "2", "memory": "8Gi" },
"limits": { "cpu": "3", "memory": "14Gi" }
}
}
],
"restartPolicy": "Never",
"serviceAccountName": "{{ service_account_name }}"
}
}
}
}
},
"variables": { ... keep existing ... }
}
3) Apply it:
prefect work-pool update kube-test --base-job-template base.json
Option B — expose variables so you can set them per deployment
Edit the base job template to add variables and template them in:
{
"variables": {
"... keep existing ...": "...",
"node_selector": { "type": "object", "default": {} },
"container_resources": { "type": "object", "default": {} }
},
"job_configuration": {
"job_manifest": {
"spec": {
"template": {
"spec": {
"nodeSelector": {{ node_selector | tojson }},
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"args": "{{ command }}",
"env": "{{ env }}",
"resources": {{ container_resources | tojson }}
}
],
"restartPolicy": "Never"
}
}
}
}
}
}
Then your deployment YAML can actually drive it:
work_pool:
name: "kube-test"
job_variables:
namespace: "prefect3"
node_selector:
"<http://node.kubernetes.io/instance-type|node.kubernetes.io/instance-type>": "g4dn.xlarge"
container_resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "3"
memory: "14Gi"
Sanity checks:
- Inspect an actual Job the worker created to confirm the spec was applied:
kubectl get job -n prefect3 -l <http://prefect.io/work-queue-name=<your-queue|prefect.io/work-queue-name=<your-queue>> -o yaml
- If those nodes are tainted, add matching tolerations in the pod spec.
- If you truly need GPU on g4dn, request it explicitly:
resources:
limits:
<http://nvidia.com/gpu|nvidia.com/gpu>: "1"
and ensure the NVIDIA device plugin is installed.
- Make sure there’s capacity in that node group; the scheduler won’t conjure a g4dn out of thin air. Sadly.Ritesh
08/12/2025, 11:57 AMMarvin
08/12/2025, 11:57 AMMarvin
08/12/2025, 11:59 AMprefect work-pool get-default-base-job-template --type kubernetes -f base.json
2) Edit base.json and add the nodeSelector and container resources under job_configuration.job_manifest.spec.template.spec and the container. Be sure the label key is quoted exactly.
{
"job_configuration": {
"job_manifest": {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"generateName": "{{ name }}-",
"labels": "{{ labels }}",
"namespace": "{{ namespace }}"
},
"spec": {
"backoffLimit": 0,
"template": {
"spec": {
"nodeSelector": {
"<http://node.kubernetes.io/instance-type|node.kubernetes.io/instance-type>": "g4dn.xlarge"
},
"containers": [
{
"name": "prefect-job",
"args": "{{ command }}",
"env": "{{ env }}",
"image": "{{ image }}",
"imagePullPolicy": "{{ image_pull_policy }}",
"resources": {
"requests": { "cpu": "2", "memory": "8Gi" },
"limits": { "cpu": "3", "memory": "14Gi" }
}
}
],
"parallelism": 1,
"completions": 1,
"restartPolicy": "Never",
"serviceAccountName": "{{ service_account_name }}"
}
},
"ttlSecondsAfterFinished": "{{ finished_job_ttl }}"
}
},
"command": "{{ command }}",
"env": "{{ env }}",
"labels": "{{ labels }}",
"name": "{{ name }}",
"namespace": "{{ namespace }}",
"cluster_config": "{{ cluster_config }}",
"job_watch_timeout_seconds": "{{ job_watch_timeout_seconds }}",
"pod_watch_timeout_seconds": "{{ pod_watch_timeout_seconds }}",
"stream_output": "{{ stream_output }}"
},
"variables": { ... leave the existing variables as-is ... }
}
3) Apply the updated template to your pool
prefect work-pool update kube-test --base-job-template base.json
4) Ensure your deployment sets the namespace (the default template already supports it)
In your deployment (prefect.yaml or deploy call), set:
work_pool:
name: "kube-test"
job_variables:
namespace: "prefect3"
5) Run a test flow and verify
- Trigger a run:
prefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME> --watch
- Inspect the created job/pod:
kubectl -n prefect3 get pod -l job-name -o name
kubectl -n prefect3 get pod <pod-name> -o jsonpath='{.spec.nodeName}{"\n"}{.spec.nodeSelector}{"\n"}'
kubectl -n prefect3 get pod <pod-name> -o jsonpath="{.spec.nodeSelector['<http://node.kubernetes.io/instance-type|node.kubernetes.io/instance-type>']}{'\n'}"
You should see the nodeSelector applied and the pod scheduled on a g4dn.xlarge node. If the node group is tainted, add tolerations in the same template block.
Option B — flexible per-deployment (expose variables)
1) Export the default template (as above) and modify it to add variables and template them into the manifest:
{
"variables": {
"... keep existing ...": "...",
"node_selector": { "type": "object", "default": {} },
"container_resources": { "type": "object", "default": {} }
},
"job_configuration": {
"... keep existing fields ...": "...",
"job_manifest": {
"spec": {
"template": {
"spec": {
"nodeSelector": {{ node_selector | tojson }},
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"args": "{{ command }}",
"env": "{{ env }}",
"resources": {{ container_resources | tojson }}
}
],
"restartPolicy": "Never",
"serviceAccountName": "{{ service_account_name }}"
}
}
}
}
}
}
Marvin
08/12/2025, 11:59 AMprefect work-pool update kube-test --base-job-template base.json
3) Set values in your deployment
work_pool:
name: "kube-test"
job_variables:
namespace: "prefect3"
node_selector:
"<http://node.kubernetes.io/instance-type|node.kubernetes.io/instance-type>": "g4dn.xlarge"
container_resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "3"
memory: "14Gi"
4) Deploy and test
prefect deploy
prefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME> --watch
Then verify with kubectl as shown above.
Notes that save you from hair-pulling:
- The key must be exactly "node.kubernetes.io/instance-type" (quoted). Slack auto-links break it.
- Your earlier job_variables.job_spec
is ignored; the Kubernetes worker expects job_manifest
in the base job template. That’s why nothing happened.
- If you need GPU on g4dn, also add:
resources:
limits:
<http://nvidia.com/gpu|nvidia.com/gpu>: "1"
and make sure the NVIDIA device plugin is installed.
- If nodes are tainted, add matching tolerations in the pod spec.
If you paste your current pool’s base job template, I’ll mark exactly where to insert the lines.Ritesh
08/12/2025, 12:30 PMMarvin
08/12/2025, 12:30 PMMarvin
08/12/2025, 12:31 PMprefect worker start --pool kube-test --name kube-test-local
- It will use the pool’s base job template to submit Jobs to your cluster. Make sure this machine has cluster access (kubeconfig or in-cluster creds) and Prefect API auth set (Cloud or Server).
Option 2 — run the worker inside the Kubernetes cluster (recommended)
- Use the Helm chart to deploy a long‑lived Kubernetes worker Deployment that polls your pool.
- Minimal values.yaml example:
worker:
enabled: true
name: kube-test-worker
workPool:
name: kube-test
type: kubernetes
# Optionally bake in your base job template or leave it to the pool config
baseJobTemplate: {} # or paste your customized template here
# Point to Prefect Cloud workspace or self-hosted server
prefectApiUrl: "<https://api.prefect.cloud/api/accounts/<acct>/workspaces/<ws>>" # or your server URL
prefectApiKeySecret:
create: true
name: prefect-api-key
key: key
value: "<YOUR_API_KEY>"
- Install:
helm repo add prefect <https://prefecthq.github.io/prefect-helm>
helm repo update
helm upgrade --install kube-test-worker prefect/prefect-agent -f values.yaml -n prefect3 --create-namespace
Notes
- The only mandatory flag for the CLI method is --pool
. Add --work-queue
if you want to restrict to certain queues.
- If your pool is paused, resume it:
prefect work-pool resume kube-test
- Confirm the worker is connected and polling in the UI, then trigger a run:
prefect deployment run <FLOW>/<DEPLOYMENT> --watch
Tell me where you’re running this (local vs cluster) and whether you’re on Cloud or self-hosted, and I’ll give you the exact command/values with the right API URL and secrets.Ritesh
08/13/2025, 5:51 AMMarvin
08/13/2025, 5:52 AMMarvin
08/13/2025, 5:53 AMjob_variables
.
- Workers don’t have a YAML config. They just pick up jobs from a work pool. The only “allocation” at the worker level is how many flow runs it will process concurrently, which you set when starting the worker (not in deployment YAML).
- You don’t need a separate YAML for the work pool either.
Typical pattern:
- Work pool: define the base job template for your infrastructure (Docker, Kubernetes, ECS, Process) — image, env, cpu/memory, etc.
- Deployment YAML: optionally override parts of that template per deployment using job_variables
.
Example sketch (keys depend on your infra type; check your pool’s “Base job template” to see the exact variable names to use):
deployments:
- name: my-flow
work_pool:
name: my-k8s-pool
job_variables:
image: myrepo/myflow:latest
cpu: "1"
memory: "1Gi"
env:
PREFECT_LOGGING_LEVEL: INFO
If you tell me which work pool type you’re using (Kubernetes, Docker, ECS, Process), I’ll show the exact fields to set.
Docs for reference:
- Work pools and base job templates
- Deployments and job_variablesRitesh
08/13/2025, 6:53 AMMarvin
08/13/2025, 6:53 AMMarvin
08/13/2025, 6:54 AMRitesh
08/13/2025, 9:14 AMMarvin
08/13/2025, 9:14 AMMarvin
08/13/2025, 9:18 AM--limit
flag. It controls how many flow runs that worker will claim/launch concurrently (each as its own Kubernetes Job).
Examples:
# Local
prefect worker start -p my-k8s-pool --limit 4
# If you need to specify the type explicitly
prefect worker start -p my-k8s-pool --worker-type kubernetes --limit 4
Running the worker in Kubernetes? Add the flag to the container args:
apiVersion: apps/v1
kind: Deployment
metadata:
name: prefect-k8s-worker
spec:
replicas: 1
template:
spec:
containers:
- name: worker
image: prefecthq/prefect:3-latest
command: ["prefect", "worker", "start"]
args: ["-p", "my-k8s-pool", "--limit", "4"]
Notes:
- Work pool and work queue concurrency limits also exist and cap throughput across all workers:
- Work pool: prefect work-pool set-concurrency-limit "my-k8s-pool" 20
- Work queue: prefect work-queue set-concurrency-limit "queue-a" 10
- Changing the worker limit requires restarting that worker.
- This doesn’t set CPU/memory; that’s in the work pool’s base job template (overridable via deployment job_variables
). Thrilling, I know.
Docs:
- Workers
- Work pools and concurrencyRitesh
08/13/2025, 1:14 PMMarvin
08/13/2025, 1:14 PMMarvin
08/13/2025, 1:17 PMjob_variables.image
to a project-specific image with dependencies baked in.
deployments:
- name: project-a
work_pool:
name: my-k8s-pool
job_variables:
image: <http://ghcr.io/acme/project-a:2025-08-12|ghcr.io/acme/project-a:2025-08-12>
- name: project-b
work_pool:
name: my-k8s-pool
job_variables:
image: <http://ghcr.io/acme/project-b:2025-08-12|ghcr.io/acme/project-b:2025-08-12>
Kubernetes nodes will cache the image; with imagePullPolicy: IfNotPresent
, subsequent runs won’t re-pull unless the tag changes.
2) Separate work pools per project (optional)
- Create a k8s work pool per project with its own base job template (image, env, service account, tolerations, etc.).
- Useful if projects need different cluster policies, not required for dependencies alone.
If you truly don’t want to build images
- You can mount a project-specific virtualenv from a PersistentVolume via job_variables.pod_overrides
and point your command/entrypoint at it. It works, but it’s more fragile and slower than using images. Containers were invented to avoid exactly this.
Helpful commands
- Inspect the K8s base job template to see variable names you can override (e.g., image
, imagePullPolicy
, env
, pod_overrides
):
prefect work-pool get-default-base-job-template --type kubernetes
- Build images (use your CI or Docker directly; Prefect also offers a helper):
prefect dev build-image
Docs
- Work pools and base job templates: Work pools
- Deployments and job_variables: Deployments
If you share how you build/run your flows today, I can sketch the exact prefect.yaml
and any pod_overrides
you’d need.Ritesh
08/14/2025, 5:32 AMMarvin
08/14/2025, 5:32 AMMarvin
08/14/2025, 5:35 AMprefect
and your runtime deps). Nothing mystical—just containers with your stack baked in.
How it runs:
- The Docker worker pulls your image and starts a container.
- Prefect provides the command to run your flow, so you don’t need a special ENTRYPOINT/CMD (unless you really want one).
Typical setup
Dockerfile (micromamba – fast and friendly):
FROM mambaorg/micromamba:1.5.8
ARG MAMBA_DOCKERFILE_ACTIVATE=1
SHELL ["/bin/bash", "-lc"]
# Add env spec
COPY environment.yml /tmp/environment.yml
# Create env and install Prefect + deps
RUN micromamba create -y -n flow -f /tmp/environment.yml && \
micromamba run -n flow pip install --no-cache-dir prefect && \
micromamba clean --all --yes
# Use the env by default
ENV MAMBA_DEFAULT_ENV=flow
ENV PATH=/opt/conda/envs/flow/bin:$PATH
WORKDIR /opt/prefect
# No special CMD needed; Prefect will override with the flow run command
environment.yml:
name: flow
channels:
- conda-forge
dependencies:
- python=3.11
- pip
- pip:
- your-package
- another-dep
Deploy (Prefect 3.x) pointing at your image:
from prefect import flow
# your flow(s) defined in repo...
flow.from_source(
source="<https://github.com/your-org/your-repo>",
entrypoint="path/to/flow.py:my_flow",
).deploy(
name="prod",
work_pool_name="docker-pool",
image="<http://ghcr.io/your-org/your-image:latest|ghcr.io/your-org/your-image:latest>",
)
Notes:
- Install prefect
inside the image.
- Either bake your code into the image or (recommended) use .from_source(...)
so the worker clones your repo at run time.
- If you need GPUs/system libs, include them in the image too.
References and examples:
- Prefect 3.x deployment config (prefect.yaml)
- Dockerizing Python apps (blog)
- Example: running flows on Docker
- Docker image build step example
If by “method 1” you meant something else, say the word and I’ll recalibrate my existential dread accordingly.Ritesh
08/17/2025, 1:24 PMMarvin
08/17/2025, 1:25 PMMarvin
08/17/2025, 1:27 PMDeployment
still lives at prefect.client.schemas.objects
.
That said, you rarely need to touch the schema directly in 3.x. Prefer:
- Deploying flows: Flow.from_source(...).deploy(...)
or the CLI prefect deploy
Docs: Deployments in Prefect 3
- Reading deployments: the client
from prefect.client.orchestration import get_client
async def get_dep():
async with get_client() as client:
dep = await client.read_deployment_by_name("flow-name/deployment-name")
return dep
And just to save you a future sigh: Deployment.build_from_flow()
and prefect deployment build
were removed in 3.x; use flow.from_source(...).deploy(...)
and prefect deploy
instead. Release notes: 3.x release notesRitesh
08/17/2025, 2:22 PMMarvin
08/17/2025, 2:22 PMMarvin
08/17/2025, 2:23 PMprefect config unset PREFECT_API_AUTH_STRING PREFECT_API_DATABASE_CONNECTION_URL -y
Helpful checks:
- See what’s set and where:
prefect profile inspect
- If you have multiple profiles, switch first:
prefect profile ls
prefect profile use <name>
If you also exported these as environment variables, unset them in your shell (they override profiles):
- bash/zsh:
unset PREFECT_API_AUTH_STRING PREFECT_API_DATABASE_CONNECTION_URL
- PowerShell:
Remove-Item Env:PREFECT_API_AUTH_STRING, Env:PREFECT_API_DATABASE_CONNECTION_URL
Docs: Settings & profiles