Cullen Baker
09/30/2025, 3:46 PMMarvin
09/30/2025, 3:46 PMMarvin
09/30/2025, 3:49 PMFlow.from_source(...).deploy(...).
- Example:
from prefect import flow
from prefect.flows import Flow
@flow
def my_flow():
print("Hello from prod")
if __name__ == "__main__":
Flow.from_source(
source="<https://github.com/your-org/your-repo>", # or a local path
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="my-flow-prod",
work_pool_name="prod-process-pool",
cron="0 * * * *", # or interval=3600, etc.
tags=["prod"],
)
Notes:
- source can be a git URL, local path, or other supported storage types. The entrypoint is path/to/file.py:flow_func_name.
- To containerize runs (Docker/K8s), specify image=... and optionally build=True, push=True in .deploy(...).
2) CLI-first
- Use the prefect deploy command to register a deployment from an entrypoint on disk or from a checked-out repo.
prefect deploy flows/my_flow.py:my_flow -n my-flow-prod -p prod-process-pool --cron "0 * * * *"
Tip: You can add parameters, tags, and schedules via flags. (If you’d like, I can tailor an exact command for your setup.)
Setting up your production host
1) Create a work pool for your chosen worker type
- Process example:
prefect work-pool create --type process prod-process-pool
- Docker or K8s pools require the corresponding plugin/extras to be installed on the host that will run the worker.
2) Start a worker that points to the pool
- Process:
prefect worker start --pool prod-process-pool
- Docker or K8s:
- Ensure Docker/K8s and the Prefect integration are installed on the host
- Start the worker similarly, pointing to the correct pool
3) Run the worker as a service (systemd example)
- Create a unit file like `/etc/systemd/system/prefect-worker.service`:
[Unit]
Description=Prefect Worker - prod
After=network.target
[Service]
Type=simple
User=prefect
WorkingDirectory=/opt/prefect
Environment="PREFECT_API_URL=<YOUR_API_URL>"
# Or PREFECT_API_KEY for Prefect Cloud:
# Environment="PREFECT_API_KEY=***"
ExecStart=/usr/bin/bash -lc 'source /opt/venvs/prefect/bin/activate && prefect worker start --pool prod-process-pool'
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
Then:
```
sudo systemctl daemon-reload
sudo systemctl enable prefect-worker
sudo systemctl start prefect-worker
sudo systemctl status prefect-workerMarvin
09/30/2025, 3:49 PMFlow.from_source("<https://github.com/...>") with an entrypoint.
- Local path: good for on-host code in a controlled deployment.
- Container images: for Docker/K8s, specify image= in deploy(...). You can let Prefect build/push or bring your own CI-built image.
Important note about Prefect 2.x vs 3.x
- Prefect 3.x removes:
- Deployment.build_from_flow() (do not use in 3.x)
- prefect deployment build CLI (replaced by prefect deploy)
- GitHub storage blocks (use .from_source("<https://github.com/...>") instead)
- If you’re still on 2.x, consider upgrading. If not, you can still use workers in 2.x, but the 3.x UX is streamlined.
Questions to tailor a concrete setup for you
- What OS and environment is your “production host” (VM, bare metal, Docker host, or K8s cluster)?
- Are you aiming for containerized execution (Docker/K8s) or simple subprocess execution on the host?
- Will you connect this host to Prefect Cloud or a self-hosted Prefect server?
- Do you need schedules, parameters, or concurrency limits for these deployments?
If you share those details, I’ll give you the exact commands, a systemd file aligned to your path/venv, and a minimal example to deploy your flows.Cullen Baker
09/30/2025, 7:09 PMMarvin
09/30/2025, 7:09 PMMarvin
09/30/2025, 7:10 PMfrom prefect import flow
from prefect.flows import Flow
@flow
def my_flow():
...
Flow.from_source(
source="<https://github.com/your-org/your-repo>",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="my-flow-prod",
work_pool_name="prod-process-pool",
cron="0 * * * *",
)
- Start a Process worker on the host (as a service is recommended).
- Dynamic (Docker worker)
- Deploy with containerized runs:
from prefect import flow
from prefect.flows import Flow
@flow
def my_flow():
...
Flow.from_source(
source="<https://github.com/your-org/your-repo>",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="my-flow-prod",
work_pool_name="prod-docker-pool",
image="<http://ghcr.io/your-org/your-image:prod|ghcr.io/your-org/your-image:prod>", # or build/push via Prefect
build=True, # optional if you want Prefect to build
push=True, # optional if you want Prefect to push
cron="0 * * * *",
)
- Start a Docker worker on the host (ensure the host can pull the image tag).
Helpful references
- Workers overview and concepts: Work pools and workers
- Background/context: Work pools and workers: deploy Python securelyMarvin
09/30/2025, 7:10 PMCullen Baker
09/30/2025, 7:19 PMMarvin
09/30/2025, 7:19 PMMarvin
09/30/2025, 7:21 PMprefect work-pool create --type process prod-process-pool
2) Start a worker on the box (as a service is best). Example systemd unit using your Conda env:
[Unit]
Description=Prefect Process Worker (prod)
After=network.target
[Service]
Type=simple
User=prefect
WorkingDirectory=/opt/prefect
Environment="PREFECT_API_URL=<YOUR_API_URL>" # or PREFECT_API_KEY for Cloud
ExecStart=/usr/bin/bash -lc 'source /opt/conda/etc/profile.d/conda.sh && conda activate myenv && prefect worker start --pool prod-process-pool'
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable prefect-worker
sudo systemctl start prefect-worker
3) Deploy your flows pointing at this pool:
from prefect import flow
from prefect.flows import Flow
@flow
def my_flow():
...
Flow.from_source(
source="<https://github.com/your-org/your-repo>",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="my-flow-prod",
work_pool_name="prod-process-pool",
cron="0 * * * *",
tags=["prod"],
)
Pros:
- Direct local path access
- No container/image overhead
- Single shared Conda env matches your use case
Option B: Dynamic (Docker work pool using Podman)
Use if you need isolation or resource limits.
1) Enable Podman’s Docker-compatible API
- Rootless (recommended; service runs under the same user as the worker):
systemctl --user enable --now podman.socket
# socket is typically at: unix:///run/user/$UID/podman/podman.sock
- Rootful:
sudo systemctl enable --now podman.socket
# socket typically at: unix:///run/podman/podman.sock
2) Ensure the worker can talk to Podman by setting DOCKER_HOST
- For a user service (rootless), in your systemd unit:
Environment="DOCKER_HOST=unix:///run/user/%i/podman/podman.sock"
Replace %i with the actual UID or set a fixed path. Alternatively, set it in the ExecStart shell.
3) Log in to your JFrog registry (do this as the same user that runs the worker):
podman login <your-jfrog-registry>
4) Create a Docker work pool:
prefect work-pool create --type docker prod-docker-pool
5) Configure volume mounts for local paths
- Add mounts at the pool’s base job template or per-deployment via job variables. On RHEL with SELinux, use :Z (private label) or :z (shared). Example job variable you can add at deploy time:
```
job_variables={Marvin
09/30/2025, 7:21 PM6) Build/push a single shared image with your Conda env (in CI or locally), push to JFrog, then reference it in your deployments:
from prefect import flow
from prefect.flows import Flow
@flow
def my_flow():
...
Flow.from_source(
source="https://github.com/your-org/your-repo",
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="my-flow-prod",
work_pool_name="prod-docker-pool",
image="<jfrog-registry>/your-team/your-image:prod", # prebuilt in CI
# or let Prefect build/push for you: build=True, push=True
cron="0 * * * *",
tags=["prod"],
# job_variables={"volumes": ["/mnt/data/mnt/dataZ"]},
)
7) Start the Docker worker as a service under the same user that owns the Podman socket:
[Unit]
Description=Prefect Docker Worker (prod via Podman)
After=network.target
[Service]
Type=simple
User=prefect
WorkingDirectory=/opt/prefect
Environment="PREFECT_API_URL=<YOUR_API_URL>"
Environment="DOCKER_HOST=unix:///run/user/1001/podman/podman.sock" # example UID
ExecStart=/usr/bin/bash -lc 'source /opt/venvs/prefect/bin/activate && prefect worker start --pool prod-docker-pool'
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
```
Notes and best practices
- SELinux: With Podman on RHEL, remember :Z or :z on volume mounts; verify host path labels with ls -Z.
- Permissions: Ensure the user running the worker can read/write the mounted host paths.
- Registry access: podman login for the worker’s user; use immutable image tags for reproducibility.
- Concurrency: With 50–100 flows, consider setting work pool/deployment concurrency limits and, if needed, run more than one worker process on the host(s).
- Migration flexibility: You can start with Process worker and later re-deploy the same flows to a Docker pool by adding an image and volume mounts to your deployments.
Docs
- Concepts overview: Work pools and workers
- Deploying from source: Deploy from source
- Schedules, parameters, and concurrency on deployments: Deployments overview
If you’d like, tell me:
- The exact local directories your flows need, and
- Whether you prefer rootless or rootful Podman
… and I’ll produce a ready-to-drop-in systemd unit and deployment snippets for either path.Cullen Baker
09/30/2025, 8:05 PMMarvin
09/30/2025, 8:05 PMMarvin
09/30/2025, 8:07 PMFlow.from_source(...).deploy(
name="my-flow-prod",
work_pool_name="prod-process-pool",
concurrency_limit=2,
# schedule, tags, etc.
)
- Global (tag-based) limits: Create global limits by tag to control shared resources across many flows (e.g., anything that touches a specific directory or external system). Docs: Global concurrency limits.
- Multiple workers (optional): You can run more than one process worker on the same host (or multiple hosts) pointing to the same pool to increase throughput—but still respect your pool/global limits.
Operational tips
- Stagger schedules: If many flows start at the top of the hour, offset them by a few minutes to avoid spikes.
- Right-size concurrency: Choose a pool limit that matches CPU, RAM, and I/O headroom. Each flow run is a separate Python process; budget memory accordingly.
- Monitor and adjust: Start conservatively, observe CPU/mem/I/O and flow durations, then raise limits gradually.
- OS limits: Ensure ulimit/open files and process limits are reasonable for your expected concurrency.
- Isolation needs: If later you need per-run CPU/memory limits or stricter reproducibility, you can switch to a Docker (Podman) pool and keep your deployment definitions.
If you share:
- CPU cores and RAM on the box
- Whether most flows are CPU-bound or I/O-bound
- Any particularly heavy flows we should isolate
…I’ll propose concrete concurrency numbers (pool + per-deployment) and a schedule staggering plan.Cullen Baker
09/30/2025, 8:38 PMMarvin
09/30/2025, 8:38 PMMarvin
09/30/2025, 8:49 PMprefect deploy
- Deploy a subset by name pattern (globs supported):
prefect deploy --name "etl-*"
- Deploy one deployment by exact name:
prefect deploy --name "reports/daily"
Example prefect.yaml with multiple deployments and cron schedules
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: process-pool
parameters:
env: dev
schedules:
- cron: "0 * * * *"
timezone: "UTC"
- name: reports/daily
entrypoint: flows/reports.py:build_reports
work_pool: process-pool
parameters:
env: prod
schedules:
- cron: "0 6 * * *"
timezone: "America/New_York"
Operational notes for “static infrastructure”
- You still need a worker running against the target work pool so scheduled runs will execute. For a static, on-host process runner:
prefect worker start -p process-pool
- You generally don’t need to “start” each flow. After you deploy from prefect.yaml, the Prefect scheduler will enqueue runs according to your schedules/triggers and the worker will pick them up.
- If you want to kick one off immediately:
prefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME>
Example:
prefect deployment run etl/etl-hourly
Docs and references
- Prefect YAML (multiple deployments, structure): prefect.yaml guide
- Creating deployments: Create deployments
- Schedules: Create schedules
- Deployment triggers/automations (optional, event-based): Creating deployment triggers
Given your environment
- For true “static” local access to filesystem paths on your RHEL9 host, use the Process work pool; flows will run on the same machine as the worker and can read/write local paths directly.
- If you choose containers (Podman), you can still use prefect.yaml to deploy many at once, but you’ll need to mount host directories into the container for local path access.
Would you like an example prefect.yaml tailored to your setup (Process vs Podman, shared conda env, JFrog image names, etc.)?Cullen Baker
09/30/2025, 8:59 PMCullen Baker
09/30/2025, 8:59 PMMarvin
09/30/2025, 8:59 PMMarvin
09/30/2025, 9:01 PMprefect work-pool create docker-pool --type docker
- Start a worker on a machine that has Docker or a Docker-compatible runtime (Podman) and can pull your images:
prefect worker start -p docker-pool
Note:
- Install integrations on the worker host: pip install "prefect[docker]"
- For Podman on RHEL9, either install podman-docker (Docker-compatible socket) or set DOCKER_HOST to your Podman socket (e.g., unix:///run/user/$UID/podman/podman.sock). Make sure you’re logged in to JFrog (docker login or podman login) so the worker can pull images.
2) Define deployments in prefect.yaml
- You can deploy many flows at once; just switch the work_pool to your Docker pool and provide Docker job variables. For example:
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: docker-pool
job_variables:
image: <http://artifactory.myco.com/prefect/flows:2025-09-30|artifactory.myco.com/prefect/flows:2025-09-30>
env:
PREFECT_LOGGING_LEVEL: INFO
APP_ENV: dev
# Bind-mount host paths needed by the flow (important if flows read local files)
volumes:
- /srv/data:/srv/data:Z
# Optional network
network: analytics-net
schedules:
- cron: "0 * * * *"
timezone: "UTC"
- name: reports/daily
entrypoint: flows/reports.py:build_reports
work_pool: docker-pool
job_variables:
image: <http://artifactory.myco.com/prefect/flows:2025-09-30|artifactory.myco.com/prefect/flows:2025-09-30>
env:
APP_ENV: prod
volumes:
- /srv/reports:/srv/reports:Z
schedules:
- cron: "0 6 * * *"
timezone: "America/New_York"
Tips:
- Build your image with your shared conda environment baked in, push to JFrog, then reference it via job_variables.image.
- On RHEL9 with SELinux, use :Z on bind mounts (as shown) so containers can access labeled host paths.
- If you need more Docker settings, you can add them under job_variables (e.g., labels, auto_remove, extra_hosts, resource limits).
3) Deploy all (or a subset) from the project root
- All deployments in the file:
prefect deploy
- Filter by name pattern:
prefect deploy --name "etl-*"
- Exact one:
prefect deploy --name "reports/daily"
4) Run behavior
- After deployment, schedules will enqueue runs automatically; your Docker worker will launch containers for each run.
- Trigger an on-demand run:
prefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME>
Docs
- prefect.yaml: prefect.yaml guide
- Creating deployments: Create deployments
- Schedules: Create schedules
- Deployment triggers (optional): Creating deployment triggers
If you’d like, I can sketch:
- A minimal Dockerfile that installs your shared conda env
- A Podman-specific note (socket/env) for your RHEL9 hosts
- A prefect.yaml tailored to your JFrog registry and host pathsCullen Baker
09/30/2025, 9:10 PMCullen Baker
09/30/2025, 9:12 PMMarvin
09/30/2025, 9:12 PMMarvin
09/30/2025, 9:14 PMprefect init
- It will initialize a new deployment configuration “recipe” and create a prefect.yaml. You can pass a specific recipe and prefill fields if you know them:
prefect init --recipe docker -f key=value -f another_key=value
Notes:
- The available fields depend on the recipe; run prefect init and follow the prompts, or use -f to pass values. You can edit the resulting YAML afterward.
2) Copy your previous deployments from the old prefect.yaml into the new file, then update the infrastructure-specific bits (see below).
Option B: Manually transform your existing YAML
- Keep your flow entrypoints, parameters, schedules, triggers the same.
- Change only the infrastructure section to the new target:
- Switch work_pool to the new pool
- Adjust job_variables to the new worker/infrastructure type
Example: Process/static → Docker/dynamic
Before (Process/static)
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: process-pool
parameters:
env: dev
schedules:
- cron: "0 * * * *"
timezone: "UTC"
After (Docker/dynamic)
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: docker-pool
job_variables:
image: <http://artifactory.myco.com/prefect/flows:2025-09-30|artifactory.myco.com/prefect/flows:2025-09-30>
env:
PREFECT_LOGGING_LEVEL: INFO
APP_ENV: dev
volumes:
- /srv/data:/srv/data:Z
schedules:
- cron: "0 * * * *"
timezone: "UTC"
Recreate the work pool and worker for the new architecture
- Create the target work pool:
prefect work-pool create docker-pool --type docker
- Start a worker for it:
pip install "prefect[docker]" # on the worker host
prefect worker start -p docker-pool
Podman on RHEL9:
- Ensure Docker-compatible socket (podman-docker) or set DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock
- Use :Z on bind mounts under SELinux
- Log in to JFrog from the worker host (docker login or podman login)
Redeploy in bulk
- From your project root:
prefect deploy # deploys all deployments in prefect.yaml
prefect deploy --name "etl-*" # deploy only a subset by name pattern
Kick the tires
- Trigger one run:
prefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME>
- Watch logs in the UI or on the worker host.
Docs
- prefect.yaml guide: prefect.yaml guide
- Create deployments: Create deployments
- Schedules: Create schedules
Want me to generate a Docker-based prefect.yaml for your exact setup (JFrog image, Podman volumes, shared conda env), and a quick script to migrate 50–100 deployments by pattern? If you’re targeting something else (e.g., Kubernetes), tell me which architecture and I’ll tailor the YAML.Cullen Baker
09/30/2025, 9:21 PMMarvin
09/30/2025, 9:21 PMMarvin
09/30/2025, 9:22 PMprefect work-pool create process-pool --type process
prefect worker start -p process-pool
2) Initialize a prefect.yaml (recipe optional), then set the work_pool to your Process pool:
prefect init # then edit prefect.yaml
# (If your init prompts include a 'process' option, you can pick it; otherwise just edit the file.)
Minimal prefect.yaml for static Process
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: process-pool
parameters:
env: dev
schedules:
- cron: "0 * * * *"
timezone: "UTC"
Then deploy:
prefect deploy
Docs
- prefect.yaml guide: prefect.yaml guide
- Create deployments: Create deployments
- CLI init reference: prefect init CLI
If you ever decide to switch to Docker later, you’ll mainly update work_pool and add job_variables (e.g., image, volumes).Cullen Baker
09/30/2025, 10:17 PMMarvin
09/30/2025, 10:17 PMMarvin
09/30/2025, 10:18 PMprefect work-pool create process-pool --type process
prefect worker start -p process-pool
2) Run prefect init
- If prompted for a recipe: pick “Process” if shown. If it’s not listed, choose “No” and we’ll hand-edit the YAML.
3) Minimal prefect.yaml for static Process
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: process-pool
parameters:
env: dev
schedules:
- cron: "0 * * * *"
timezone: "UTC"
- name: reports/daily
entrypoint: flows/reports.py:build_reports
work_pool: process-pool
parameters:
env: prod
schedules:
- cron: "0 6 * * *"
timezone: "America/New_York"
4) Deploy all or some:
prefect deploy # all deployments in prefect.yaml
prefect deploy --name "etl-*" # subset by pattern
Tips for your setup
- Ensure the worker process’s environment points at your shared Conda env (PATH/conda activation in the service/systemd unit).
- Since flows need local filepaths, Process worker is the simplest path on RHEL9.
Docs
- prefect.yaml: prefect.yaml guide
- Create deployments: Create deployments
If you want, I can provide a systemd unit example for the Process worker on RHEL9 that activates your Conda env and sets environment variables.Cullen Baker
09/30/2025, 10:22 PMMarvin
09/30/2025, 10:22 PMMarvin
09/30/2025, 10:23 PMprefect work-pool create process-pool --type process
prefect worker start -p process-pool
2) Run prefect init
- Choose “local” if available. If you pick “default,” that’s fine—just edit the YAML afterward.
3) Edit prefect.yaml to point deployments at your Process pool:
prefect:
version: 3.0
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: process-pool
parameters:
env: dev
schedules:
- cron: "0 * * * *"
timezone: "UTC"
- name: reports/daily
entrypoint: flows/reports.py:build_reports
work_pool: process-pool
parameters:
env: prod
schedules:
- cron: "0 6 * * *"
timezone: "America/New_York"
4) Deploy all (or a subset):
prefect deploy
# or
prefect deploy --name "etl-*"
Notes for your setup
- Make sure the worker runs under your shared Conda env (activate it in the shell or systemd unit that starts the worker).
- Using Process keeps flows on the same host, so they can read/write your local filepaths directly.
Docs: prefect.yaml guide | Create deployments | Create schedules
If you’d like, I can tailor a prefect.yaml to your exact repo layout and add a small systemd unit example that activates your Conda env before starting the worker.Cullen Baker
10/01/2025, 7:41 PMpull:
- prefect.deployments.steps.set_working_directory:Marvin
10/01/2025, 7:41 PMMarvin
10/01/2025, 7:47 PMset_working_directory pull step.
Option 1 (recommended): Don’t use a pull step; set the working dir via job_variables for Process runs
- For static (Process) runs, you can set the working directory directly on the job. This avoids hard-coding absolute paths in a pull step.
- In your deployment, use:
deployments:
- name: etl/hourly
entrypoint: flows/etl.py:etl
work_pool: process-pool
job_variables:
working_dir: "/opt/prefect/projects/myproj" # prod path here
- In dev, either:
- keep a dev-specific YAML with a different working_dir, or
- run a dev work pool with a different base job template default (so you don’t have to change YAML).
Option 2: Use a single YAML with a relative working directory
- If your dev and prod both run the worker from the project root, you can avoid environment-specific paths by using a relative working dir:
pull:
- prefect.deployments.steps.set_working_directory:
directory: "."
- This is the simplest if your project layout is the same and the worker starts in the repository root on both machines.
Option 3: Keep separate YAMLs per environment and select at deploy time
- Maintain two files (e.g., prefect.dev.yaml and prefect.prod.yaml) and choose the appropriate one when deploying.
- In prod, set the absolute path; in dev, set your dev path. Deploy using the correct file:
prefect deploy --prefect-file prefect.prod.yaml
# and in dev:
prefect deploy --prefect-file prefect.dev.yaml
Option 4: Two deployments (dev/prod) in the same YAML
- Define both with different work_pool and job_variables.working_dir, then deploy both or just a subset:
deployments:
- name: etl/hourly-dev
work_pool: process-dev
job_variables:
working_dir: "/home/devuser/projects/myproj"
- name: etl/hourly-prod
work_pool: process-prod
job_variables:
working_dir: "/opt/prefect/projects/myproj"
- Deploy a subset:
prefect deploy --name "*/hourly-prod"
Notes
- For static Process runs, setting job_variables.working_dir is usually cleaner than using the pull.set_working_directory step.
- If you do keep the pull step, prefer relative paths when possible. Absolute paths often force environment-specific YAML.
- If you want to standardize even further, you can set the Process worker’s default working directory via the work pool’s base job template, then omit it from deployments.
Helpful docs
- Prefect YAML: prefect.yaml guide
- Creating deployments: Create deployments
If you tell me your dev and prod paths, I can produce a minimal pair of YAMLs (or a single YAML with two deployments) that aligns with your RHEL9 + shared Conda setup.Cullen Baker
10/01/2025, 8:09 PMMarvin
10/01/2025, 8:09 PMMarvin
10/01/2025, 8:12 PMprefect:
version: 3.0
deployments:
- name: ingest/hourly
entrypoint: ./flows/ingest.py:ingest
work_pool: process-pool
job_variables:
working_dir: /opt/prefect/projects/myproj
env:
APP_ENV: prod
parameters:
source: s3
schedules:
- cron: "0 * * * *"
timezone: "UTC"
- name: transform/daily
entrypoint: ./flows/transform.py:transform
work_pool: process-pool
job_variables:
working_dir: /opt/prefect/projects/myproj
schedules:
- cron: "0 5 * * *"
timezone: "UTC"
- name: quality/checks
entrypoint: ./flows/quality.py:run_checks
work_pool: process-pool
job_variables:
working_dir: /opt/prefect/projects/myproj
parameters:
severity: high
- name: reports/daily
entrypoint: ./flows/reports.py:build_reports
work_pool: process-pool
job_variables:
working_dir: /opt/prefect/projects/myproj
env:
APP_ENV: prod
schedules:
- cron: "0 6 * * *"
timezone: "America/New_York"
- name: archive/monthly
entrypoint: ./flows/archive.py:archive
work_pool: process-pool
job_variables:
working_dir: /opt/prefect/projects/myproj
schedules:
- cron: "0 3 1 * *"
timezone: "UTC"
Notes
- name should be unique per deployment; using flow-name/deploy-name is a good convention.
- entrypoint is the path to your file and flow function.
- job_variables.working_dir is the easiest way to set the working directory for static Process runs; you can omit the pull.set_working_directory step if you use this.
- schedules are optional; include them only where needed.
Deploy all five from your project root:
prefect deploy
Or deploy a subset by name pattern:
prefect deploy --name "*/daily"
Docs for reference
- prefect.yaml: prefect.yaml guide
- Create deployments: Create deployments
- Schedules: Create schedules
If you share the actual flow file paths and preferred names, I can generate a ready-to-paste prefect.yaml for your five flows.Cullen Baker
10/01/2025, 8:59 PMMarvin
10/01/2025, 8:59 PMMarvin
10/01/2025, 9:05 PMprefect init will scaffold the file, but you still define the deployments yourself.
Good ways to automate it anyway
Option A: Generate deployments programmatically (no YAML editing)
- Write a tiny Python script that creates many deployments at once using Prefect’s deployment API, then run it when you need to (CI/CD or local).
# scripts/deploy_all.py
from prefect.deployments import deploy
from prefect.deployments.runner import RunnerDeployment
COMMON = {
"work_pool_name": "process-pool",
"job_variables": {"working_dir": "/opt/prefect/projects/myproj"},
}
deploy(
RunnerDeployment(name="ingest/hourly", entrypoint="flows/ingest.py:ingest", **COMMON),
RunnerDeployment(name="transform/daily", entrypoint="flows/transform.py:transform", **COMMON),
RunnerDeployment(name="quality/checks", entrypoint="flows/quality.py:run_checks", **COMMON),
RunnerDeployment(name="reports/daily", entrypoint="flows/reports.py:build_reports", **COMMON),
RunnerDeployment(name="archive/monthly", entrypoint="flows/archive.py:archive", **COMMON),
)
- Run it:
python scripts/deploy_all.py
This bypasses editing prefect.yaml and still deploys many flows in one go.
Option B: Auto-generate the YAML file from a list
- If you want the deployments to live in prefect.yaml, have a small script generate the YAML for you from a Python list/dict. For example:
# scripts/generate_prefect_yaml.py
import yaml
COMMON = {
"work_pool": "process-pool",
"job_variables": {"working_dir": "/opt/prefect/projects/myproj"},
}
deployments = [
{"name": "ingest/hourly", "entrypoint": "./flows/ingest.py:ingest", **COMMON},
{"name": "transform/daily", "entrypoint": "./flows/transform.py:transform", **COMMON},
{"name": "quality/checks", "entrypoint": "./flows/quality.py:run_checks", **COMMON},
{"name": "reports/daily", "entrypoint": "./flows/reports.py:build_reports", **COMMON},
{"name": "archive/monthly", "entrypoint": "./flows/archive.py:archive", **COMMON},
]
doc = {"prefect": {"version": "3.0"}, "deployments": deployments}
with open("prefect.yaml", "w") as f:
yaml.safe_dump(doc, f, sort_keys=False)
- Then:
python scripts/generate_prefect_yaml.py
prefect deploy
Option C: Keep separate dev/prod YAMLs and choose at deploy time
- Maintain prefect.dev.yaml and prefect.prod.yaml (different working_dir, env vars, etc.), then:
prefect deploy --prefect-file prefect.prod.yaml
Note: If you prefer one file, you can put both dev/prod deployments in the same YAML and deploy subsets with --name patterns.
Why not CLI auto-discovery?
- Prefect 3.x does not provide a command that crawls your repo to detect all @flow functions and output a deployments list. You can script this yourself (e.g., maintain a registry/list of flows) and then either:
- call the deployment API (Option A), or
- render the YAML (Option B).
Docs for reference
- prefect.yaml guide: prefect.yaml guide
- Create deployments: Create deployments
If you share your flow entrypoints and desired names, I can generate a ready-to-paste prefect.yaml or a small generator script tailored to your dev/prod paths.Cullen Baker
10/01/2025, 10:34 PMdeployments:
- name: associate_allocations
tags: ["allocations"]
description: null
schedules:
- cron: "0 0 * * *"
timezone: "America/Chicago"
active: false
flow_name: "Associate Program Allocations"
entrypoint: flows/allocations/associate.py:allocate_associate
work_pool: maestro-process-pool
- name: opex_plan_load
tags: ["plan_load", "anaplan"]
description: null
schedules:
- cron: "2 6,12,18 * * *"
timezone: "America/Chicago"
active: true
flow_name: "Plan OpEx Load"
entrypoint: flows/anaplan/plan/opex1.py:load_opex
work_pool: maestro-process-pool
- name: cap_sw_plan_load
tags: ["plan_load", "anaplan"]
description: null
schedules:
- cron: "0 6,12,18 * * *"
timezone: "America/Chicago"
active: true
flow_name: "Plan Cap SW Load"
entrypoint: flows/anaplan/plan/cap_sw_3.py:load_cap_sw
work_pool: maestro-process-pool
- name: new_hire_import
tags: ["anaplan"]
description: null
schedules:
- cron: "0 8 * * 1-5"
timezone: "America/Chicago"
active: false
flow_name: "New Hire Anaplan Upload"
entrypoint: flows/anaplan/other/new_hire_import.py:upload_new_hires
work_pool: maestro-process-pool
- name: virtue_load
tags: ["competitors"]
description: null
schedules:
- cron: "30 7 * * 1-5"
timezone: "America/Chicago"
active: false
flow_name: "Extract Virtu Financials"
entrypoint: flows/competitors/virtue_load.py:extract_virtu_finacials
work_pool: maestro-process-pool
- name: seating_capacity
tags: ["office_services"]
description: null
schedules:
- cron: "0 6 * * 1-5"
timezone: "America/Chicago"
active: false
flow_name: "Update Seating Capacity"
entrypoint: flows/office_services/seating_capacity.py:update_seating_capacity
work_pool: maestro-process-pool
- name: area_account_location
tags: ["validations"]
description: null
schedules:
- cron: "0 7 * * 1-5"
timezone: "America/Chicago"
active: false
flow_name: "Check Area Account Location"
entrypoint: flows/validations/area_account_location.py:check_area_account_location
work_pool: maestro-process-pool
- name: compare_coupa_sun
tags: ["validations"]
description: null
schedules:
- cron: "30 21 * * 1-5"
timezone: "America/Chicago"
active: false
flow_name: "Compare Coupa and SUN Files"
entrypoint: flows/validations/compare_coupa_sun.py:check_area_account_location
work_pool: maestro-process-poolMarvin
10/01/2025, 10:34 PMMarvin
10/01/2025, 10:36 PMwork_pool: maestro-process-pool
to:
work_pool:
name: maestro-process-pool
# optional:
# work_queue_name: default
# job_variables:
# working_dir: /opt/prefect/projects/myproj
# env:
# APP_ENV: prod
2) If you kept a top-level pull/build/push section from the recipe, ensure they’re lists of step objects
Example of a valid pull section:
pull:
- prefect.deployments.steps.set_working_directory:
directory: /opt/prefect/projects/myproj
If pull is set to a string or otherwise malformed, merging will fail with the same error.
A cleaned-up example using your deployments
Try this minimal structure for one deployment first; if it works, replicate for the others:
prefect:
version: 3.0
deployments:
- name: associate_allocations
flow_name: "Associate Program Allocations"
entrypoint: flows/allocations/associate.py:allocate_associate
tags: ["allocations"]
work_pool:
name: maestro-process-pool
# job_variables:
# working_dir: /opt/prefect/projects/myproj
schedules:
- cron: "0 0 * * *"
timezone: "America/Chicago"
active: false
If you want to keep all 8, here’s how one more looks:
- name: opex_plan_load
flow_name: "Plan OpEx Load"
entrypoint: flows/anaplan/plan/opex1.py:load_opex
tags: ["plan_load", "anaplan"]
work_pool:
name: maestro-process-pool
schedules:
- cron: "2 6,12,18 * * *"
timezone: "America/Chicago"
active: true
Troubleshooting checklist
- Ensure work_pool is a mapping with name (and optional work_queue_name/job_variables).
- If you kept recipe steps at the top-level (pull/build/push), confirm they’re lists of step objects as shown above.
- Test with a single deployment in the file to isolate issues, then add the rest back.
- Optional: remove the active flags temporarily to see if the error persists (you can also pause a deployment with a top-level paused: true).
One more note: you’re running Python 3.13. If you continue to hit odd toolchain issues, consider testing with Python 3.11/3.12 where Prefect is most battle-tested today.
If you paste your full file (including any top-level pull/build/push sections), I’ll mark up the exact lines to change. Docs for reference:
- prefect.yaml: prefect.yaml guide
- Create deployments: Create deploymentsCullen Baker
10/01/2025, 10:44 PMMarvin
10/01/2025, 10:44 PMMarvin
10/01/2025, 10:45 PMprefect work-pool ls
prefect work-pool inspect maestro-process-pool
- Confirm type=process (for static), paused=False, and at least one connected worker.
2) Start a worker for that pool
prefect worker start -p maestro-process-pool
- Make sure you start it on the host where you want flows to run, in the right Conda env, and with the same Prefect API settings as you used to deploy.
3) If you use work queues, make sure they match
- If your deployment sets work_pool.work_queue_name, start the worker with that queue:
prefect worker start -p maestro-process-pool -q <your-queue>
4) Validate the deployment config
- Ensure your prefect.yaml uses the structured work pool form:
work_pool:
name: maestro-process-pool
# work_queue_name: default
# job_variables:
# working_dir: /opt/prefect/projects/myproj
5) Confirm API/profile on the worker host
- The worker must point to the same Prefect API URL/key as your deploy step (Cloud or Server). If needed, set PREFECT_API_URL (and PREFECT_API_KEY for Cloud) in the worker’s environment.
6) Check the UI details
- Click into a deployment in the UI and look at the Readiness section—it will usually say exactly why it’s “Not Ready” (e.g., “No workers connected to work pool maestro-process-pool”).
If you run the two commands below and paste the outputs, I can pinpoint the issue:
prefect work-pool inspect maestro-process-pool
prefect worker start -p maestro-process-pool
Docs for reference
- prefect.yaml guide: prefect.yaml guide
- Create deployments: Create deploymentsCullen Baker
10/02/2025, 2:19 PMMarvin
10/02/2025, 2:19 PMMarvin
10/02/2025, 2:20 PMprefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME> --watch
Checklist to fix the usual suspects
- Confirm the worker is in the right Conda env (same packages your flows need). If you’re using systemd, ensure the unit activates the env before prefect worker start.
- Set an explicit working directory for Process runs so imports/relative paths work:
work_pool:
name: maestro-process-pool
job_variables:
working_dir: /opt/prefect/projects/myproj
env:
APP_ENV: prod
# add any required credentials/vars here
- Verify each entrypoint is correct relative to the working_dir, e.g. flows/allocations/associate.py:allocate_associate exists on the worker host.
- Make sure any local filepaths your flows read actually exist on the host and the worker’s user has permissions to read them.
- Start the worker with more logs to catch early failures:
prefect worker start -p maestro-process-pool --log-level DEBUG
- Double-check the worker’s API profile matches the one used at deploy (Cloud vs Server URL, API key if Cloud).
- If you’re on Python 3.13, be aware some dependencies may not be fully compatible yet. If crashes persist without clear error logs, try 3.11/3.12 where Prefect is most exercised.
If you can paste:
- The first 20–30 lines from one crashed run’s logs, and
- Your current work_pool section from prefect.yaml (including job_variables)
…I’ll pinpoint the issue and propose exact changes.
Reference:
- prefect.yaml guide: prefect.yaml guide
- Create deployments: Create deploymentsCullen Baker
10/02/2025, 2:35 PMMarvin
10/02/2025, 2:35 PMMarvin
10/02/2025, 2:36 PMprefect:
version: 3.0
deployments:
- name: new_hire_import
flow_name: "New Hire Anaplan Upload"
entrypoint: flows/anaplan/other/new_hire_import.py:upload_new_hires
tags: ["anaplan"]
work_pool:
name: maestro-process-pool
job_variables:
working_dir: /opt/maestro # <-- absolute path on the prod host
env:
APP_ENV: prod
schedules:
- cron: "0 8 * * 1-5"
timezone: "America/Chicago"
active: false
Option B: Keep a pull step that sets the working directory
- If you want to keep the recipe steps, set an absolute directory:
pull:
- prefect.deployments.steps.set_working_directory:
directory: /opt/maestro
- Make sure there are no conflicting build/push/pull steps that try to use a temp directory.
After updating:
- Redeploy:
prefect deploy
- Test one deployment interactively:
prefect deployment run <FLOW_NAME>/<DEPLOYMENT_NAME> --watch
Also double-check:
- Your entrypoint is correct relative to the working_dir (e.g., /opt/maestro/flows/anaplan/other/new_hire_import.py exists).
- The worker is running on the same host, in your shared Conda env, with permissions to read those paths.
Docs
- prefect.yaml guide: prefect.yaml guide
- Create deployments: Create deployments
If you share the actual path to your project on the prod host, I can tweak your YAML to set working_dir everywhere and remove any recipe steps that are forcing the temp directory.Cullen Baker
10/02/2025, 2:48 PMpull:
- prefect.deployments.steps.set_working_directory:
directory: "."Marvin
10/02/2025, 2:48 PMMarvin
10/02/2025, 2:50 PMdeployments:
- name: new_hire_import
entrypoint: flows/anaplan/other/new_hire_import.py:upload_new_hires
work_pool:
name: maestro-process-pool
job_variables:
working_dir: /opt/maestro # absolute path to your project on the host
Option B: keep the pull step but use an absolute path
pull:
- prefect.deployments.steps.set_working_directory:
directory: /opt/maestro
Notes
- Pick one approach to avoid confusion; I generally prefer job_variables.working_dir for Process runs.
- Relative paths in your entrypoints are resolved from the working directory you set. So ensure /opt/maestro/flows/... actually exists on the worker host.
- After updating, redeploy and test one run:
prefect deploy
prefect deployment run <FLOW>/<DEPLOYMENT> --watch
If you share the actual path to your project on that host, I can adjust your YAML snippet precisely.