Dedicated image
Building a dedicated image is a build-time pattern that works with both Docker and Kubernetes deployments. Rather than mounting or injecting the workflow file at runtime, you embed it directly into the container image.
The resulting image is self-contained and requires no volume mounts, no ConfigMaps and no inline workflow configuration at runtime.
What you will learn
- Why this approach works with the official Zigflow base image
- How to write a Dockerfile that embeds your workflow
- When this build pattern is useful
- The security posture of the Zigflow container image
- How to configure Kubernetes security contexts for the worker
- How to handle writable paths when using a read-only root filesystem
- How to verify the image before deploying
How it works
The official Zigflow image sets one environment variable by default:
WORKFLOW_FILE=/app/workflow.yaml
When Zigflow starts, it loads the file at the path specified by WORKFLOW_FILE.
Because this is already set in the base image, a container that copies a workflow
file to /app/workflow.yaml will start the worker immediately. No -f flag is
required and no override is needed.
Single-file mode (default): copy one workflow file to /app/workflow.yaml.
Zigflow loads it via WORKFLOW_FILE.
Multi-file mode: to load multiple workflow files from a directory, set
WORKFLOW_DIRECTORY in your Dockerfile. If you do not want to load the default
single file, also set WORKFLOW_FILE=:
FROM ghcr.io/zigflow/zigflow
ENV WORKFLOW_FILE=
ENV WORKFLOW_DIRECTORY=/app/workflows
COPY ./workflows /app/workflows
Each distinct document.taskQueue in those files gets its own Temporal worker
and task queue.
You can also combine both: set WORKFLOW_FILE and WORKFLOW_DIRECTORY together.
All discovered files are merged and deduplicated before startup.
When using directory mode, the directory should contain workflow definitions only. Non-workflow YAML/JSON files will be treated as workflows and may cause startup errors.
If a configured file does not exist, Zigflow will fail at startup. There is no implicit fallback or silent skip.
Dockerfile
FROM ghcr.io/zigflow/zigflow
COPY ./workflow.yaml /app/workflow.yaml
Build it:
docker build -t your-registry/your-image:your-tag .
Nothing else is required in the Dockerfile. The base image already defines the
entrypoint and the WORKFLOW_FILE path.
Running the image
The workflow file is already present inside the image. Deploy it as you would
any other Zigflow container. No -f flag and no volume mount are required.
- Docker: run with
docker runor Docker Compose - Kubernetes: use Option 3 in the Helm chart workflow delivery options
Production use
This approach is well suited to production environments for the following reasons:
Immutable deployments. The workflow definition is part of the image. Once built, the image cannot be modified without producing a new build. This eliminates drift between the running container and a separately managed file.
Versioning tied to the image tag. Rolling back a workflow version is the same operation as rolling back the image tag. There is no separate artefact to track.
No runtime file mounts. Volume mounts and ConfigMap projections introduce a dependency between the running container and external configuration. Baking the workflow into the image removes that dependency.
Simpler CI/CD. The build pipeline produces a single artefact. Deployment consists of updating the image reference. There is nothing to synchronise.
Security posture
The official ghcr.io/zigflow/zigflow image is built on
Chainguard's Wolfi, a minimal
Linux distribution with a reduced package surface and a minimal base image
without a general-purpose shell.
The image runs as a dedicated non-root user (zigflow, UID 1000). It contains
only the Zigflow binary, Node.js and Python for script task support, and the CA
certificate bundle.
Script and shell task execution
Script and shell tasks execute code directly on the Zigflow worker process.
Any workflow that uses run tasks with a script or shell interpreter runs
that code inside the worker container, with access to the same environment,
network and mounted secrets as the worker itself.
Apply least-privilege principles:
- Do not mount credentials or secrets that the workflow does not require.
- Restrict network egress at the pod or network-policy level if workflows should not make arbitrary outbound connections.
- Treat the worker with the same trust boundaries you would apply to any code execution environment.
Writable paths
By default, Zigflow writes only to /tmp during script task execution.
When readOnlyRootFilesystem: true is set, /tmp must be provided as a
writable volume. Mount an emptyDir at /tmp:
volumes:
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 32Mi
# inside the container spec:
volumeMounts:
- mountPath: /tmp
name: tmp
The Helm chart configures this volume automatically. If you are writing your own manifests, add the mount shown above.
Kubernetes security context
The Helm chart ships with the following security context defaults. If you are deploying with the chart, these are applied automatically. If you are writing your own manifests, apply these settings directly.
podSecurityContext:
runAsNonRoot: true
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
A minimal Kubernetes Deployment applying these contexts alongside the
/tmp volume looks like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-workflow
spec:
template:
spec:
securityContext:
runAsNonRoot: true
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: zigflow
image: your-registry/your-image:your-tag
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /tmp
name: tmp
volumes:
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 32Mi
Resource limits should be configured to constrain CPU and memory usage for script and shell tasks.
Verifying the image
Set the VERSION environment variable to the Zigflow version
you want to use. This requires v0.9.0 or later.
Vulnerability scanning
Scan the image for known vulnerabilities before deploying:
trivy image \
--severity HIGH,CRITICAL \
--ignore-unfixed \
ghcr.io/zigflow/zigflow:$VERSION
Zigflow's CI pipeline scans each published image at build time. HIGH and CRITICAL findings are treated as build failures. Images are only published when that check passes.
The --ignore-unfixed flag filters out vulnerabilities that do not yet have a
published fix, reducing noise from upstream issues.
Helm chart config scanning
To check your Helm chart configuration for security misconfigurations:
trivy config \
--severity HIGH,CRITICAL \
./charts/zigflow
Signature verification (optional)
Images published from the main branch are signed with Cosign using keyless signing via GitHub Actions OIDC. A CycloneDX SBOM is generated with Syft and attached to the image reference.
To verify the signature:
cosign verify ghcr.io/zigflow/zigflow:$VERSION \
--certificate-identity-regexp="https://github.com/zigflow/zigflow" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
To inspect the attached SBOM:
cosign download sbom ghcr.io/zigflow/zigflow:$VERSION
Signature verification is optional but recommended when deploying in environments with strict supply chain requirements.
Related pages
- Deploying overview: connection flags and telemetry
- Docker: runtime file mounts and Docker Compose
- Kubernetes: Helm chart deployment