Nicholas Pini
06/14/2026, 11:37 AMMarvin
06/14/2026, 11:37 AMMarvin
06/14/2026, 11:39 AMimage job variable.
Minimal example:
python
from prefect import flow
from prefect_kubernetes.decorators import kubernetes
@kubernetes(
work_pool="my-k8s-pool",
image="<http://123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0|123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0>",
image_pull_policy="IfNotPresent",
namespace="prefect"
)
@flow
def my_flow():
print("running in my existing ECR image")
if __name__ == "__main__":
my_flow()
A few important notes:
* image should be the full ECR image URI
* your image still needs Prefect installed, since Prefect will execute the flow inside that container
* your Kubernetes cluster must be able to pull from ECR
For private ECR images, the Kubernetes job usually also needs imagePullSecrets configured in the work pool's base job template. That part is not typically set directly on the decorator — it’s usually configured on the Kubernetes work pool.
Typical pattern:
1. Create a Kubernetes secret for ECR auth
2. Add imagePullSecrets to the work pool's base job template
3. Use @kubernetes(..., image="...") on the flow
If you're deploying with prefect.yaml, the equivalent is:
yaml
deployments:
- name: my-deployment
entrypoint: flows.py:my_flow
work_pool:
name: my-k8s-pool
job_variables:
image: <http://123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0|123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0>
image_pull_policy: IfNotPresent
A couple caveats:
* If your flow code is not baked into the image, Prefect still needs a way to access it at runtime
* Prefer pinned tags like :1.0.0 instead of :latest
* If you update the same tag repeatedly, use image_pull_policy="Always"
Relevant docs:
* `prefect_kubernetes.decorators` reference
* Work pools
* Kubernetes deployment docs
If you want, I can also show you a full working example for:
* ECR + Kubernetes work pool setup, or
* flow code baked into the image vs pulled from Git/S3Nicholas Pini
06/14/2026, 11:44 AMMarvin
06/14/2026, 11:44 AMMarvin
06/14/2026, 11:47 AM@kubernetes decorator itself is mainly about *where the flow run executes*; it does not by itself redefine your repo layout or magically switch into your uv subproject
* For monorepos, flow.from_source(...) or deployment pull steps are usually clearer than relying only on @kubernetes
Example monorepo:
text
repo-root/
services/
libs/
flows/
pyproject.toml
uv.lock
my_flow.py
If GitHub pulls the whole repo, then your entrypoint is typically:
text
flows/my_flow.py:my_flow
not just:
text
my_flow.py:my_flow
because Prefect resolves it relative to the pulled source root unless you explicitly change the working directory first.
Recommended pattern
If the flow belongs to a subfolder uv project, use from_source(...).deploy(...) and make the entrypoint explicit:
python
from prefect import flow
@flow
def my_flow():
print("hello")
if __name__ == "__main__":
my_flow.from_source(
source="<https://github.com/my-org/my-monorepo.git>",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="my-flow",
work_pool_name="my-k8s-pool",
image="<http://123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0|123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0>",
)
A few important details:
* If your Docker image already contains all dependencies, that’s great — but the flow source code still needs to line up with the runtime path Prefect uses
* If the image also already contains the repo code at the correct path, you may not want runtime Git pulling at all
* If Prefect does pull from GitHub at runtime, it clones the repo and resolves the entrypoint from that checkout
About the working directory
There are basically two cases:
1. Entrypoint includes the subfolder path
* Example: flows/my_flow.py:my_flow
* This is the simplest and most reliable for monorepos
2. You change the working directory before resolving the entrypoint
* Then you could use my_flow.py:my_flow
* This is more common with deployment pull steps like set_working_directory
For example in `prefect.yaml`:
yaml
pull:
- prefect.deployments.steps.git_clone:
repository: <https://github.com/my-org/my-monorepo.git>
branch: main
- prefect.deployments.steps.set_working_directory:
directory: my-monorepo/flows
deployments:
- name: my-flow
entrypoint: my_flow.py:my_flow
work_pool:
name: my-k8s-pool
job_variables:
image: <http://123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0|123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0>
Without that set_working_directory, you’d usually want:
yaml
entrypoint: flows/my_flow.py:my_flow
Important uv / monorepo caveat
If your uv project is in a subfolder, Prefect does not automatically activate that subproject just because the flow file is there.
So if your runtime behavior depends on:
* pyproject.toml in the subfolder
* local package imports from that subfolder layout
* uv run semantics
* relative file access
then you should make sure one of these is true:
1. Your image already has the dependencies installed, and your flow imports work regardless of cwd
2. Your code is installed as a package in the image
3. You set the working directory to the uv project subfolder before execution
In practice, the most robust setup is often:
* bake dependencies into the ECR image
* optionally bake the flow code in too
* install the subproject as a package in the image
* use explicit entrypoints
That avoids depending on runtime cwd quirks.
For @kubernetes specifically
The decorator is great for telling Prefect “run this flow on my Kubernetes work pool with these job variables”, like:
```python
from prefect import flowMarvin
06/14/2026, 11:47 AMsubfolder/file.py:flow_fn
* If you explicitly change cwd to the subfolder first: use file.py:flow_fn
If you want, I can give you one of these next:
1. a complete prefect.yaml example for monorepo + uv subfolder + existing ECR image
2. a Python-only from_source(...).deploy(...) example
3. guidance on baking the monorepo subproject into the image so Git pull isn’t needed at runtimeNicholas Pini
06/14/2026, 11:48 AMMarvin
06/14/2026, 11:48 AMMarvin
06/14/2026, 11:48 AM@kubernetes decorator itself in a monorepo:
The decorator does not give you a way to set a repo subdirectory / working directory like a deployment pull step does.
What it really does is submit the flow run to a Kubernetes work pool and pass job variables like image, namespace, etc. It’s about infrastructure, not source checkout semantics.
So in a monorepo, the important distinction is:
* @kubernetes(...) controls where the flow runs
* your Python execution/import context still depends on how that flow file is being invoked and what exists in the container at runtime
If your flow file is inside a monorepo subfolder, then for @kubernetes to work cleanly, one of these should be true:
* the image already contains the monorepo code in a layout that preserves imports
* the relevant subproject is installed into the image as a package
* the runtime command/environment in the container already starts in a usable cwd
Because with the decorator alone, there is not a built-in “run this flow from subdirectory `foo/bar`” parameter.
Example monorepo:
text
repo/
libs/
services/
prefect_flows/
pyproject.toml
my_flow.py
If my_flow.py does imports like:
python
from prefect_flows.some_module import x
then your image needs to support that import path. The safest way is usually to install that subproject into the image rather than relying on cwd being repo/prefect_flows.
So instead of depending on working directory, prefer:
dockerfile
COPY prefect_flows /app/prefect_flows
WORKDIR /app
RUN pip install -e /app/prefect_flows
or otherwise ensure the package is importable.
Then your flow can be:
python
from prefect import flow
from prefect_kubernetes.decorators import kubernetes
@kubernetes(
work_pool="my-k8s-pool",
image="<http://123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0|123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image:1.0.0>",
)
@flow
def my_flow():
...
Key point: with @kubernetes, don’t design around “Prefect will cd into my monorepo subfolder”. That’s not really the model.
Instead, design so that the container can run the flow without needing a special cwd, by doing one of:
* install the subproject as a package
* copy the code into a stable path in the image and make imports absolute
* set PYTHONPATH in the image if needed
If you rely on relative file access like:
python
open("./config.yaml")
that’s fragile in this setup. Better:
python
from pathlib import Path
CONFIG = Path(__file__).parent / "config.yaml"
So the practical answer is:
In a monorepo, the Kubernetes decorator can use your existing ECR image just fine, but it does not manage subdirectory entrypoint/cwd behavior for you. Make the monorepo subproject importable inside the image, and avoid depending on runtime cwd.
If you want, I can sketch the recommended image layout for a monorepo subproject using uv so @kubernetes works reliably.Nicholas Pini
06/14/2026, 12:06 PMMarvin
06/14/2026, 12:06 PMMarvin
06/14/2026, 12:08 PM@kubernetes decorator does not primarily locate your flow by an entrypoint path like file.py:flow.
Instead, when you call the flow, Prefect takes the actual Flow object in memory and submits that for remote execution.
So this:
python
@kubernetes(work_pool="my-pool")
@flow
def my_flow():
...
works roughly like this:
1. You call my_flow()
2. The decorator intercepts that call
3. Prefect creates a flow run
4. Prefect serializes the flow object itself using cloudpickle
5. It creates a bundle containing:
* the serialized flow
* flow run metadata
* execution context
* dependency metadata
6. That bundle is uploaded to storage used by the work pool
7. The Kubernetes worker starts a pod
8. The pod runs a Prefect command that downloads the bundle
9. Prefect deserializes the flow object
10. It executes the original flow function with the provided parameters
So the answer to:
“How does it know where the function is?”
is:
It usually doesn’t look it up by file path at runtime for the Kubernetes decorator path — it already has the function object and ships that object over as a serialized bundle.
That’s the important distinction from deployment/source-based execution, where Prefect uses an entrypoint string and imports code from files or Git.
With @kubernetes(...), the remote side runs something conceptually like:
bash
uv run -m prefect.bundles.execute --key <bundle-key>
That bundle contains the serialized flow.
So for decorator-based Kubernetes runs:
* not primarily “import repo/subdir/file.py, find `my_flow`”
* instead “deserialize this flow object and run it”
Why this matters for your monorepo question:
* Prefect is not depending on repo-relative entrypoint resolution in the same way
* But your container still needs an environment where deserializing and executing that flow works
* That means the image still needs compatible Python/package dependencies
* And if your flow references local modules/files in ways that require a specific filesystem layout, those still need to exist
So although Prefect knows which function to run from the serialized flow object, that does not mean all referenced code/data magically comes along unless it’s part of what gets bundled or already exists in the image.
In one sentence:
For @kubernetes, Prefect knows what to run because it serializes the in-memory flow object and sends that bundle to the Kubernetes job, rather than resolving the flow from an entrypoint string at runtime.
If you want, I can next explain:
* what parts of a monorepo flow are actually captured by that bundle, versus
* what still must already exist in the image/filesystem