Python Integration
Consul Guardian runs as a sidecar next to your Python application. Your app reads configuration from Consul KV using python-consul2 or any HTTP client. Guardian independently watches those same keys, commits every change to Git, and lets you restore any key to a previous value.
No code changes are required in your Python application.
How Python apps typically read from Consul
| Package | Description |
|---|---|
| python-consul2 | Maintained fork of python-consul |
| requests | Direct HTTP calls to Consul API |
Example: python-consul2
pip install python-consul2
import consul
c = consul.Consul(host="consul", port=8500)
def get_config(key, default=None):
_, data = c.kv.get(key)
if data is None:
return default
return data["Value"].decode("utf-8")
# Read config at startup
DB_HOST = get_config("config/myapp/db_host", "localhost")
DB_PORT = int(get_config("config/myapp/db_port", "5432"))
CACHE_TTL = int(get_config("config/myapp/cache_ttl", "300"))
Example: Flask app
from flask import Flask
import consul
app = Flask(__name__)
c = consul.Consul(host="consul", port=8500)
def load_config():
"""Load all config values from Consul KV."""
keys = {
"config/myapp/db_host": "DB_HOST",
"config/myapp/db_port": "DB_PORT",
"config/myapp/secret_key": "SECRET_KEY",
"config/myapp/debug": "DEBUG",
}
for consul_key, config_key in keys.items():
_, data = c.kv.get(consul_key)
if data is not None:
app.config[config_key] = data["Value"].decode("utf-8")
load_config()
@app.route("/health")
def health():
return {"status": "ok", "db_host": app.config.get("DB_HOST")}
Example: Django settings
# settings.py
import consul
c = consul.Consul(host="consul", port=8500)
def consul_get(key, default=""):
_, data = c.kv.get(key)
if data is None:
return default
return data["Value"].decode("utf-8")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": consul_get("config/django/db_host", "localhost"),
"PORT": consul_get("config/django/db_port", "5432"),
"NAME": consul_get("config/django/db_name", "myapp"),
"USER": consul_get("config/django/db_user", "postgres"),
"PASSWORD": consul_get("config/django/db_password"),
}
}
CACHE_TTL = int(consul_get("config/django/cache_ttl", "300"))
Adding Guardian
Docker Compose
version: "3.8"
services:
consul:
image: hashicorp/consul:1.17
ports:
- "8500:8500"
command: agent -dev -client=0.0.0.0
myapp:
build: .
ports:
- "5000:5000"
environment:
- CONSUL_HOST=consul
- CONSUL_PORT=8500
depends_on:
- consul
guardian:
image: ghcr.io/consul-guardian/consul-guardian:latest
environment:
- CONSUL_GUARDIAN_CONSUL_ADDRESS=http://consul:8500
- CONSUL_GUARDIAN_WATCH_PREFIXES=config/
- CONSUL_GUARDIAN_GIT_REPO_PATH=/data/repo
volumes:
- guardian-data:/data
depends_on:
- consul
volumes:
guardian-data:
Celery + Consul config
If your Celery workers read broker URLs and task configuration from Consul KV, Guardian protects those values too:
version: "3.8"
services:
consul:
image: hashicorp/consul:1.17
command: agent -dev -client=0.0.0.0
redis:
image: redis:7
web:
build: .
command: gunicorn app:app --bind 0.0.0.0:5000
environment:
- CONSUL_HOST=consul
depends_on:
- consul
- redis
worker:
build: .
command: celery -A tasks worker --loglevel=info
environment:
- CONSUL_HOST=consul
depends_on:
- consul
- redis
guardian:
image: ghcr.io/consul-guardian/consul-guardian:latest
environment:
- CONSUL_GUARDIAN_CONSUL_ADDRESS=http://consul:8500
- CONSUL_GUARDIAN_WATCH_PREFIXES=config/,celery/
- CONSUL_GUARDIAN_GIT_REPO_PATH=/data/repo
volumes:
- guardian-data:/data
depends_on:
- consul
volumes:
guardian-data:
Guardian watches both config/ and celery/ prefixes. If someone changes celery/broker_url to a wrong Redis URL, you can restore it in seconds.
Restore scenario
Someone updates the database host to point at a decommissioned server:
consul kv put config/myapp/db_host "old-db.internal"
Your Flask app starts returning 500 errors. Django returns "OperationalError: could not connect to server".
1. Find what changed
consul-guardian log --key config/myapp/db_host
# 2024-03-15T16:00:00Z bad1234 config/myapp/db_host modified
# 2024-02-01T09:00:00Z good567 config/myapp/db_host modified
2. Restore
consul-guardian restore --key config/myapp/db_host --commit good567
3. Restart your app (Python apps typically read config at startup)
docker compose restart myapp
# or: kill -HUP <gunicorn_pid>
The app comes back with the correct database host.
Zero code changes
Guardian does not install a Python package, does not modify your requirements.txt, and does not hook into your WSGI/ASGI pipeline. It is a separate process that watches Consul KV independently. Your Python app and Guardian have no awareness of each other.