<@ULVA73B9P> Is it good practice to pass Block (or...
# ask-marvin
p
@Marvin Is it good practice to pass Block (or block $ref) as a deployment (flow) parameter? Or does that leak credentials to the UI? What is the correct method for passing a block parameter
m
thought for 178.7 seconds
Yes — passing a Block as a flow/deployment parameter is generally a good and safe pattern in Prefect 3.x. Short version: - If you pass a saved block as a parameter, Prefect stores a block reference, not the block’s contents. - That means credentials inside the block are not exposed in deployment parameters or the UI. - The correct pattern is to pass either: - the block itself (when already loaded/saved), or - a block name / identifier and load it inside the flow, depending on your use case. What actually gets stored Prefect stores a reference like:
Copy code
json
{
  "$ref": {
    "block_document_id": "..."
  }
}
—not the actual credential values. At runtime, Prefect resolves that reference and loads the block from block storage. So this is safe relative to putting raw secrets in parameters. What you should avoid Do not pass credentials directly as normal flow parameters like:
Copy code
python
@flow
def my_flow(api_key: str):
    ...
Those kinds of values are much more likely to be surfaced in run parameters, UI views, logs, or API payloads. Recommended patterns 1. Best for shared credentials/config: use a saved block
Copy code
python
from prefect import flow
from prefect.blocks.system import Secret

@flow
def my_flow(secret_block: Secret):
    token = secret_block.get()
    print("Loaded secret at runtime")
If that block is a saved block document, Prefect can represent it by reference. 2. Also good: pass a block name, then load inside the flow
Copy code
python
from prefect import flow
from prefect.blocks.system import Secret

@flow
def my_flow(secret_block_name: str):
    secret_block = Secret.load(secret_block_name)
    token = secret_block.get()
This is often the clearest pattern operationally: - deployment parameter is just a simple string like
"prod-api-token"
- secret material stays entirely in block storage - changing environments is easy Which pattern should you choose? - Use a block parameter/ref if you want the flow signature to explicitly require a block type. - Use a block name string if you want simpler deployment parameters and easier environment switching. For many teams, passing the block name and calling
.load()
inside the flow
is the most straightforward pattern. Does it leak to the UI? Not the credentials themselves, if you are passing a saved block/reference. The UI/API may show: - that a block-backed parameter exists - a block reference / document ID - possibly the block/document name depending on how you load/use it But not the decrypted secret values. Rule of thumb - Sensitive reusable config → use Blocks - Values that vary run-to-run → use parameters - Never put raw secrets in plain parameters unless you accept that risk Relevant docs: - Blocks - Flow parameters - Deployments in Python If you want, I can also show: - a full deployment example using a block parameter, or - the recommended pattern in
prefect.yaml
/
prefect deploy
.
p
@Marvin Show me the recommended pattern in
prefect.yaml
. If passing the block via {{ prefect.... }}, the credentials are resolved at deploy-time and leaked in UI
m
thought for 153.7 seconds
Yes — your concern is correct. If you use
{{ prefect.blocks... }}
or
{{ prefect.variables... }}
in most deployment fields inside
prefect.yaml
, those values are resolved at deploy time and stored on the deployment.
That means they can show up in the deployment configuration/UI. The important exception is pull steps. Safe rule: - Secrets in
pull:
→ generally safe; resolved by the worker at runtime - Secrets in
job_variables:
or deployment
parameters:
→ *not safe*; resolved at deploy time and persisted So the recommended pattern in
prefect.yaml
is: 1. Use block refs only in
pull:
for things like Git auth
Copy code
yaml
pull:
  - prefect.deployments.steps.git_clone:
      repository: <https://github.com/my-org/my-repo.git>
      branch: main
      credentials: "{{ prefect.blocks.github-credentials.repo-creds }}"
This is the main supported secure use of block templating in
prefect.yaml
. 2. Do not inject secret-bearing blocks into
job_variables:
Avoid this:
Copy code
yaml
deployments:
  - name: my-deployment
    entrypoint: flows.py:my_flow
    work_pool:
      name: my-pool
      job_variables:
        env:
          API_TOKEN: "{{ prefect.blocks.secret.api-token }}"
Why avoid it: - it resolves during
prefect deploy
- the resolved value is stored with the deployment - it may be visible in deployment config/UI 3. Instead, pass a non-secret identifier and load the block at runtime in code Example `prefect.yaml`:
Copy code
yaml
deployments:
  - name: my-deployment
    entrypoint: flows.py:my_flow
    parameters:
      secret_block_name: api-token
    work_pool:
      name: my-pool
And in your flow:
Copy code
python
from prefect import flow
from prefect.blocks.system import Secret

@flow
def my_flow(secret_block_name: str):
    token = Secret.load(secret_block_name).get()
    # use token here
This is usually the best pattern for secrets needed by the flow itself: - deployment stores only
api-token
- secret stays in Prefect block storage - credentials are loaded only at runtime 4. If the worker/infrastructure itself needs secrets, prefer external secret injection For infrastructure-level auth, use your runtime platform’s secret mechanisms instead of storing resolved secrets in deployment config: - Kubernetes Secrets - ECS task secrets - Docker / CI environment variables - cloud-native secret managers Then reference them as normal environment variables in the running infrastructure. Example idea:
Copy code
yaml
deployments:
  - name: my-deployment
    entrypoint: flows.py:my_flow
    work_pool:
      name: k8s-pool
      job_variables:
        env:
          API_TOKEN: "{{ $API_TOKEN }}"
But note:
{{ $API_TOKEN }}
is still taken from the environment where you run
prefect deploy
. So this is only appropriate if you knowingly want that value baked into the deployment. It is not a runtime-secret lookup. So for true runtime secret handling, prefer: - platform-native secret injection into the worker/job, or - loading Prefect blocks inside the flow/task code Recommended
prefect.yaml
pattern summary
For code retrieval secrets:
Copy code
yaml
pull:
  - prefect.deployments.steps.git_clone:
      repository: <https://github.com/my-org/my-private-repo.git>
      credentials: "{{ prefect.blocks.github-credentials.repo-creds }}"
For flow secrets:
Copy code
yaml
deployments:
  - name: my-deployment
    entrypoint: flows.py:my_flow
    parameters:
      db_credentials_block: prod-db-creds
Copy code
python
from prefect import flow
from prefect_aws.credentials import AwsCredentials

@flow
def my_flow(db_credentials_block: str):
    creds = AwsCredentials.load(db_credentials_block)
What I recommend in practice - Never put
{{ prefect.blocks... }}
in
job_variables
if the block contains secrets - Use
pull:
block templating only for deployment source retrieval/auth - For secrets your flow uses, pass the block name as a plain parameter and call
.load()
in the flow
- For secrets your infrastructure uses, inject them from the execution environment, not
prefect.yaml
Docs: - Blocks - prefect.yaml - Flow parameters - Work pools and job configuration If you want, I can show a full secure example for one of these cases: - private Git repo auth in
pull:
- API key used inside the flow - Kubernetes worker with runtime-injected secrets
p
@Marvin So it's not possible to pass the block in the param? Only the slug?
m
thought for 6.6 seconds
You can pass a Block as a flow parameter — but there’s an important distinction: - Passing a block object/reference as a run parameter is supported - Setting that parameter from
prefect.yaml
using
{{ prefect.blocks... }}
is not the safe/recommended way for secret-bearing blocks, because it resolves at deploy time So the answer is: Yes, a flow parameter can be a Block type.
Copy code
python
from prefect import flow
from prefect.blocks.system import Secret

@flow
def my_flow(secret_block: Secret):
    token = secret_block.get()
But: No, you generally should not populate that parameter in
prefect.yaml
with
{{ prefect.blocks... }}
for secrets.
Because in
prefect.yaml
, deployment parameters are resolved during deployment creation, and the resolved value gets stored on the deployment. So in practice, the recommended deployment-safe pattern is usually: - pass the block name (not necessarily “slug” — usually the saved block document name) - then call
.load(name)
inside the flow Example:
Copy code
yaml
deployments:
  - name: my-deployment
    entrypoint: flows.py:my_flow
    parameters:
      secret_block_name: my-api-token
Copy code
python
from prefect import flow
from prefect.blocks.system import Secret

@flow
def my_flow(secret_block_name: str):
    secret_block = Secret.load(secret_block_name)
    token = secret_block.get()
A small terminology note: - block type slug = something like
secret
,
s3-bucket
,
aws-credentials
- block document name = the saved instance name, like
my-api-token
or
prod-s3
- what you usually pass is the block document name So the concise answer is: - Possible in Python/runtime? Yes, you can pass a Block parameter. - Recommended in
prefect.yaml
for secret blocks?
No. - Recommended instead? Pass the saved block’s name and load it at runtime. If you'd like, I can show the exact patterns that are safe for: -
prefect deploy
- ad hoc flow runs via API/UI - subflows calling flows with block parameters
thought for 5.4 seconds
Could you paste the full value you mean after
param:
? For example, are you asking about something like:
Copy code
yaml
parameters:
  my_block: ...
and whether
...
can be:
Copy code
yaml
my_block: "{{ prefect.blocks.secret.my-secret }}"
or:
Copy code
yaml
my_block:
  $ref:
    block_document_id: "..."
or something else? Short answer in advance: - If
param:
is a
prefect.yaml
deployment parameter, then anything templated with
{{ prefect.blocks... }}
will be resolved at deploy time
, so not recommended for secret-bearing blocks. - If you manually pass a raw block
$ref
object at run creation time via API/Python, that’s a different case and can work without exposing the secret contents. If you paste the exact YAML/Python snippet, I can tell you whether it’s safe.
p
@Marvin I was asking about $ref. Will the params be visible then in the UI via Run -> Parameters
m
thought for 7.8 seconds
Yes — the parameter value itself will be visible in the UI under Run → Parameters. If you pass a block by
$ref
, what shows up there is the reference object, e.g.
Copy code
json
{
  "$ref": {
    "block_document_id": "..."
  }
}
—not the block contents. So: - Will the parameter be visible? Yes - Will the secret values inside the block be visible there? No - Will the block document ID / reference be visible? Yes That means
$ref
is generally safe for credential leakage, but not fully opaque: - users who can view run parameters can see that a particular block document was referenced - they won’t see the decrypted secret values just from the parameter display So if your concern is specifically: - “Will the actual credentials leak into Run → Parameters?”No - “Will the existence/ID of the block reference be visible?”Yes Practical recommendation - If you’re creating runs programmatically and can pass a
$ref
directly, that is a valid way to pass a block parameter without exposing the block’s contents. - If you’re defining deployment defaults in
prefect.yaml
, prefer passing the block document name and loading it in code, because
prefect.yaml
templating resolves too early. Best patterns by context Programmatic run creation:
Copy code
python
client.create_flow_run(
    ...,
    parameters={
        "creds": {
            "$ref": {
                "block_document_id": "..."
            }
        }
    }
)
Safe from secret-value leakage in parameters. Deployment defaults in `prefect.yaml`:
Copy code
yaml
parameters:
  creds_block_name: prod-creds
Copy code
python
@flow
def my_flow(creds_block_name: str):
    creds = MyCredentialsBlock.load(creds_block_name)
If you want, I can show you: - a complete example of passing a block
$ref
via Python/API, or - how the UI/API payload differs between
$ref
and
{{ prefect.blocks... }}
.
p
@Marvin Why is $ref not recommended in prefect.yaml?
m
thought for 10.4 seconds
Because
prefect.yaml
is for deployment specification, not a general “store this raw object untouched” channel. A raw
$ref
in
prefect.yaml
is not the normal supported authoring pattern, and there are a few reasons it’s not what I’d recommend: 1.
prefect.yaml
is resolved and processed at deploy time
Prefect treats
prefect.yaml
as config to be rendered into a deployment definition. That means the file is subject to deploy-time processing, validation, schema generation, and serialization behavior. So even though a
$ref
object may look like a stable low-level representation,
prefect.yaml
is not really documented as “you should hand-author internal block reference objects here”. 2.
$ref
uses an internal identifier
A block
$ref
typically contains a
block_document_id
, which is: - environment-specific - not human-friendly - not portable across workspaces/accounts - brittle if you recreate the block Example:
Copy code
yaml
parameters:
  creds:
    $ref:
      block_document_id: 550e8400-e29b-41d4-a716-446655440000
That deployment now depends on a specific UUID in one workspace. If you move to another workspace or recreate the block, that ID changes. By contrast, this is much easier to manage:
Copy code
yaml
parameters:
  creds_block_name: prod-creds
then:
Copy code
python
creds = MyBlock.load("prod-creds")
3. It’s lower-level than the public workflow most users should rely on
$ref
is the serialization format Prefect uses to represent block references. It’s useful and safe, but it’s closer to an internal wire/storage representation than the most maintainable config format for humans to write. In other words: - supported conceptually? Yes - best hand-written deployment pattern? Usually no 4. It is still visible in Run → Parameters Even though secrets do not leak, the
$ref
object itself is visible in the UI/API. So if your goal is a cleaner operator experience, passing a block name is usually nicer than showing a UUID reference object. 5. Name-based loading is more portable and explicit Loading by name in code: - is easier to read - works cleanly across deployments - avoids embedding workspace-specific IDs in YAML - makes intent obvious to other engineers Example:
Copy code
yaml
parameters:
  aws_credentials_block_name: prod-aws
Copy code
python
from prefect import flow
from prefect_aws.credentials import AwsCredentials

@flow
def my_flow(aws_credentials_block_name: str):
    creds = AwsCredentials.load(aws_credentials_block_name)
That’s usually the most maintainable approach. So is
$ref
forbidden in
prefect.yaml
?
Not exactly — I’m saying it’s not the recommended pattern, mainly because it’s brittle and low-level, not because it would expose the secret contents. Summary -
$ref
in
prefect.yaml
does not leak the underlying credentials - but it is not recommended because it embeds internal block document IDs into deployment config - the preferred pattern is to pass the saved block name and call
.load()
at runtime If you want, I can give you a direct yes/no matrix for these three cases: -
prefect.yaml
+
{{ prefect.blocks... }}
-
prefect.yaml
+ raw
$ref
- API/Python run creation + raw
$ref
so you can choose the least bad option for your setup.