Markov can store workflow checkpoints in Postgres instead of a local SQLite file. Use Postgres when runs are started by Kubernetes Jobs, orchestrators, or multiple hosts that need a shared durable state store for status, list, diagram, and resume.
SQLite remains the simplest option for local development and single-process runs. Postgres is the better default for production-style Kubernetes execution because the state survives pod deletion and can be read by separate resume or inspection commands.
The state store contains only Markov checkpoint data:
for_each.It does not store workflow source files. Resume still requires the original workflow file or directory to exist at the same path recorded when the run started.
Markov selects the backend from the state store value:
| Value | Backend |
|---|---|
./markov-state.db |
SQLite |
/data/markov-state.db |
SQLite |
postgres://user:pass@host:5432/db?sslmode=disable |
Postgres |
postgresql://user:pass@host:5432/db?sslmode=disable |
Postgres |
State store precedence is:
--state-store <path-or-dsn>MARKOV_STATE_STORE/tmp/markov-state.db./markov-state.dbFor automation, prefer MARKOV_STATE_STORE so every Markov command uses the same backend without repeating the DSN:
export MARKOV_STATE_STORE='postgres://markov:markov@localhost:5432/markov_state?sslmode=disable'
markov run workflows/deploy.yaml --run-id deploy-001
markov status deploy-001 --steps
markov resume deploy-001
Use --state-store when a command needs to override the environment:
markov list --state-store 'postgres://markov:markov@db.example.com:5432/markov_state?sslmode=require'
Start a disposable Postgres container:
docker run --rm --name markov-postgres \
-e POSTGRES_USER=markov \
-e POSTGRES_PASSWORD=markov \
-e POSTGRES_DB=markov_state \
-p 5432:5432 \
postgres:16
In another shell:
export MARKOV_STATE_STORE='postgres://markov:markov@localhost:5432/markov_state?sslmode=disable'
markov run examples/simple-test.yaml --run-id pg-demo-001
markov status pg-demo-001 --steps
markov list
Markov creates its checkpoint tables automatically on first connection.
To inspect the tables directly:
docker exec -it markov-postgres \
psql -U markov -d markov_state
Useful SQL:
select run_id, entrypoint, status, started_at, completed_at
from runs
order by started_at desc;
select workflow_name, step_name, status, error
from steps
where run_id = 'pg-demo-001'
order by started_at;
For Kubernetes, pass the DSN through a Secret and set MARKOV_STATE_STORE in the Markov runner container.
Create the Secret:
kubectl create secret generic markov-state \
--from-literal=dsn='postgres://markov:REDACTED@postgres.example.svc.cluster.local:5432/markov_state?sslmode=require'
Use it in the Job or controller that starts Markov:
apiVersion: batch/v1
kind: Job
metadata:
name: markov-run-example
spec:
template:
spec:
restartPolicy: Never
containers:
- name: markov
image: markov:latest
command:
- markov
- run
- /etc/markov/workflow
- --run-id
- markov-run-example
env:
- name: MARKOV_STATE_STORE
valueFrom:
secretKeyRef:
name: markov-state
key: dsn
volumeMounts:
- name: workflow
mountPath: /etc/markov/workflow
readOnly: true
volumes:
- name: workflow
configMap:
name: markov-workflow
Now a separate inspection or resume pod can use the same Secret:
markov status markov-run-example --steps
markov resume markov-run-example
Those commands must also have the workflow source mounted at the same path when resuming.
Common DSN forms:
postgres://markov:password@postgres:5432/markov_state?sslmode=disable
postgres://markov:password@postgres:5432/markov_state?sslmode=require
postgresql://markov:password@postgres:5432/markov_state?sslmode=verify-full
Use sslmode=require or stronger for networked production databases. Use sslmode=disable only for local development or trusted in-cluster networks where transport security is handled separately.
If the password contains special characters, URL-encode it. For example, p@ss/word becomes p%40ss%2Fword.
Markov currently creates and updates its own tables. The database user needs permission to:
For a dedicated database:
create database markov_state;
create user markov with password 'REDACTED';
grant all privileges on database markov_state to markov;
For stricter installations, create a dedicated schema and grant Markov ownership of that schema. Keep Markov checkpoint tables separate from dashboard, application, or analytics tables.
Postgres solves shared durable checkpoint storage, but resume still depends on workflow stability:
run_id.for_each_key stable for production fan-outs.vars.yaml, rules.yaml, and workflow files when using directory workflows.Example:
export MARKOV_STATE_STORE='postgres://markov:markov@postgres:5432/markov_state?sslmode=require'
markov run /etc/markov/workflow --run-id release-2026-07-07
# After fixing an external issue or workflow bug:
markov resume release-2026-07-07
Check recent runs:
markov list
Check one run and its steps:
markov status release-2026-07-07 --steps
Generate a run diagram:
markov diagram release-2026-07-07
Inspect directly in SQL:
select run_id, status, started_at, completed_at
from runs
order by started_at desc
limit 20;
select run_id, workflow_name, step_name, status, left(error, 200) as error
from steps
where status = 'failed'
order by completed_at desc
limit 20;
/tmp/markov-state.dbThe command did not receive MARKOV_STATE_STORE and did not pass --state-store. Check the pod environment:
kubectl exec <pod> -- env | grep MARKOV_STATE_STORE
Also check command arguments. An explicit --state-store always wins over the environment variable.
run_id not foundThe inspection command is pointed at a different state store than the original run, or the run used local SQLite. Make sure MARKOV_STATE_STORE is identical for run, status, and resume.
The state store has the checkpoint, but the workflow source path recorded in the run is not available. Mount the workflow at the same path or run resume from an environment where that path exists.
Validate the DSN from the same network location as the Markov process:
psql 'postgres://markov:REDACTED@postgres:5432/markov_state?sslmode=require' -c 'select 1'
In Kubernetes, check DNS, NetworkPolicy, Secret values, and whether the database requires TLS.
When MARKOV_STATE_STORE is set to a Postgres DSN, Markov redacts credentials in debug/help-style output. This does not change the runtime value used to connect.
Treat the state database as operational recovery data:
There is no built-in pruning command yet, so retention is currently handled outside Markov.
SQLite is still appropriate when:
Use Postgres when: