Virtual Environments
Virtual environments in Conduit are isolated, independent versions of your pipeline. Unlike traditional systems that have a single mutable "production," Conduit lets you create multiple environments (production, staging, testing) that fork, diverge, and converge independently.
Why Virtual Environments?
In Airflow or Dagster, testing changes before production means:
- Deploy to a separate machine/cluster (expensive)
- Or modify variables/connections (risky, error-prone)
In Conduit, creating a new environment is instantaneous and zero-copy:
# Fork production → staging (instant, no data copy)
conduit env create staging --from production
Staging now has its own independent pipeline state, schedule, and run history. Changes in staging don't affect production until you promote them.
How Environments Work
Environments are snapshot pointers, not data copies:
graph TB
Snapshot["Content-addressable Snapshot<br/>f1e2d3c4b5a6"]
ProdPointer["production<br/>(points to snap-v1)"]
StagingPointer["staging<br/>(points to snap-v1 + local changes)"]
DevPointer["dev<br/>(points to snap-v1 + dev changes)"]
Snapshot -->|referenced by| ProdPointer
Snapshot -->|referenced by| StagingPointer
Snapshot -->|referenced by| DevPointer
When you create an environment, you create a new pointer to the same snapshot. No data is copied. Environment operations are O(1).
Environment Lifecycle
1. Create an Environment
Fork an existing environment:
conduit env create staging --from production
Output:
Environment created: staging
Forked from: production (snapshot: prod-snap-20240322-143215)
Current snapshot: prod-snap-20240322-143215 (shared)
Staging now has an identical pipeline to production, but is completely independent. They share the snapshot, but have separate run histories and schedules.
2. Make Changes
Modify your DAG files:
# Edit your DAG definitions
vim dags/etl.py
Compile the changes:
conduit compile
3. Plan Changes
See what would change when you apply to the environment:
conduit plan staging
Output:
Planning changes for environment: staging
Current snapshot: prod-snap-20240322-143215
Added:
- task_x (fingerprint: abc123...)
Modified:
- task_y (timeout 300 → 600)
Unchanged:
✓ task_z (will be reused from snapshot)
Summary: 1 task added, 1 task modified, 1 task reused
Estimated snapshot size: 1.2 KB (35% smaller than full)
The plan shows exactly what changed and what will be reused from the previous snapshot.
4. Apply Changes
Deploy the plan to the environment:
conduit apply staging -y
Output:
Applying plan for environment: staging
✓ Updated daily_analytics_etl
- task_x: compiled from source (new)
- task_y: compiled from source (modified)
- task_z: reused from snapshot (unchanged)
Snapshot ID: staging-snap-20240322-145123
Environment pointer: staging → staging-snap-20240322-145123
The environment now points to a new snapshot with your changes.
5. Promote to Production
When ready, promote the environment to production:
conduit env promote staging production
Output:
Promoting staging → production
Source: staging (staging-snap-20240322-145123)
Target: production (prod-snap-20240322-143215)
Backup: prod-snap-20240322-143215 archived as prod-snap-backup-20240322-145456
Environment updated: production
production → staging-snap-20240322-145123
Production now runs the new snapshot. The previous production snapshot is archived for rollback.
6. Rollback (Instant)
If something goes wrong, roll back instantly:
conduit env rollback production --to prod-snap-backup-20240322-145456
Output:
Rollback: production
From: staging-snap-20240322-145123
To: prod-snap-backup-20240322-145456
New snapshot: prod-snap-backup-20240322-145456
This is instantaneous. No data migration, no complex recovery. Just a pointer swap.
Environment Operations
List Environments
conduit env list
Output:
Available environments:
Name Status Snapshot DAGs Tasks
──────────────────────────────────────────────────────────────────────
production active prod-snap-20240322-143215 5 32
staging active staging-snap-20240322-145123 5 34
dev inactive dev-snap-20240318-092345 5 35
View Environment Details
conduit env info staging
Output:
Environment: staging
Status: active
Snapshot: staging-snap-20240322-145123
Snapshot size: 1.2 KB
Created: 2024-03-22 14:51:23 UTC
Last modified: 2024-03-22 14:51:23 UTC
Forked from: production
DAGs: daily_analytics_etl, hourly_metrics, user_segmentation, ...
Tasks: 34
Runs in last 24h: 0 (not scheduled)
Archive Environment
Archive an unused environment to save metadata space:
conduit env archive dev
Output:
Archived environment: dev
Snapshot dev-snap-20240318-092345 archived
Snapshot Management
Snapshots are the immutable artifacts that environments point to. They
live in the durable snapshot store at .conduit/snapshots_db and are
created by conduit apply (one per executed task).
There is no standalone snapshot CLI; you inspect snapshots through the environments that reference them:
# Per-task snapshot pointers for an environment (API)
curl localhost:8080/api/v1/environments/production
# Compare the pointers of two environments
conduit env diff staging production
# See how the pointers changed over time
conduit env history production
Snapshots are never deleted automatically — old versions stay available
for conduit env rollback.
Note: You cannot delete a snapshot that's referenced by an active environment.
Multi-Environment Workflows
Example: Staging + Canary Release
# 1. Create a staging environment for testing
conduit env create staging --from production
# 2. Make changes
vim dags/etl.py
conduit compile
conduit plan staging
conduit apply staging -y
# 3. Test staging for a few days
# (run the scheduler, monitor metrics, etc.)
# 4. Create a canary: run 1% of traffic to new version
conduit env create canary --from production
# (same changes as staging)
conduit apply canary -y
# 5. Monitor canary for 24 hours
# If successful:
# 6. Roll out to 10% of production via gradual promotion
conduit env promote canary production
# If issues arise:
conduit env rollback production --to prod-snap-backup-20240322-145456
Example: Feature Branch Environment
# 1. Create dev environment for feature work
conduit env create feature-new-transform --from production
# 2. Experiment freely
vim dags/transforms.py
conduit compile && conduit apply feature-new-transform -y
# 3. When ready, compare to production
conduit diff production feature-new-transform
# 4. Promote to staging first
conduit env promote feature-new-transform staging
# 5. Then to production
conduit env promote staging production
Comparing Environments
See how two environments differ:
conduit diff production staging
Output:
Comparing: production vs staging
DAGs:
✓ daily_analytics_etl (both present)
✓ hourly_metrics (both present)
+ user_segmentation (in staging only)
Tasks in daily_analytics_etl:
✓ extract (unchanged)
✓ transform (unchanged)
⚠ load (timeout 600 → 1200)
Summary:
Added: 1 DAG
Modified: 1 task
Unchanged: 9 tasks
Schedules Are Per-DAG
Schedules are declared on the DAG itself (schedule: cron expression in
the DAG definition) and executed by the scheduler inside conduit serve.
There is no per-environment schedule override: cron-initiated runs
execute against the default (production) environment. To exercise a
DAG in another environment, trigger it explicitly:
conduit run daily_analytics_etl --env staging
or over the API with {"environment": "staging"}.
Run History Carries the Environment
Every run records which environment it ran against, and history is filterable by it:
# CLI status for one environment
conduit status --env production
# API: runs filtered by environment
curl 'localhost:8080/api/v1/runs?environment=staging'
All runs share one durable event log — the environment is a recorded field on each run, not a separate stream.
Best Practices
- Always test in staging first: Catch issues before production.
- Use descriptive environment names:
staging,canary,feature-{name}, nottest1,test2. - Keep production unchanged until ready: Use plan/apply workflow, never direct edits.
- Guard production promotions with a policy:
conduit env set-policy production --require-source staging. - Document promotions: Leave notes when promoting major changes.
Limitations
- Environments are pipeline-only: They don't isolate data or external systems. A task in staging that writes to production still writes to production.
- Snapshots are immutable: Once created, a snapshot cannot be modified. Create a new one instead.
- No partial rollback: You roll back the entire environment, not individual tasks.
If you need data isolation (staging database separate from production), configure that at the task level:
@task
def write_to_db(data):
if os.getenv("CONDUIT_ENVIRONMENT") == "production":
db_connection = "prod.example.com"
else:
db_connection = "staging.example.com"
# Write to appropriate database
Next Steps
- Plan/Apply Workflow: Deep dive into fingerprinting and change detection
- CLI Reference: All environment commands
- Architecture: How snapshots and events work internally