A Gentle Introduction to Configuration Management in Drupal
Drupal's Config API is the standardized system for storing, retrieving, and synchronizing site configuration data (like views, fields, content types, and module settings) as structured YAML-serialized objects, enabling "configuration as code" across environments.drupalize+1
It distinguishes deployable, version-controlled config from transient data (like State API for cron timestamps), ensuring consistency via export/import workflows.linkedin+1
Core PHP Classes
Drupal\Core\Config\ConfigFactoryInterface (and ConfigFactory): Central service factory; use
\Drupal::config('module.settings')for read-onlyImmutableConfig, or\Drupal::service('config.factory')->getEditable('module.settings')for mutableConfigobjects to set/save values.jpstacey+2Drupal\Core\Config\Config / ImmutableConfig: Represents a single config object (e.g.,
system.site.yml); handles get/set/clear operations with dot notation for nested data.antistatique+1Drupal\Core\Config\ConfigEntityBase / ConfigEntityInterface: Base for config entities like nodes, views (e.g.,
views.view.frontpage); extends entities with exportable storage.[api.drupal]Drupal\Core\Config\StorageInterface (ActiveStorage, FileStorage): Bridges DB and YAML;
config.get()reads active storage,drush cexwrites to file storage.[drupalize]
These classes power \Drupal::config(), form storage, and sync commands like drush cim/cex.drupal+1
MySQL Tables
All config lives in one table: config (simple config + serialized entities).
No separate tables per config type—everything serialized for portability.[api.drupal]
Active config = rows in
config; export = YAML files from these rows.[drupalize]Schema validation via
config.schema.ymlfiles (type safety, defaults).[drupalize]
Usage Summary
The API ensures config is deployable (export to YAML via drush cex, commit to Git, import via cim), typed (schemas prevent bad data), and overrideable (splits, overrides). In your Acquia/DDEV flow, it's the engine behind cim in hooks and cst checks to block drift—DB rows get overwritten from Git YAML on every deploy.drupal+2
On Acquia Cloud Next, use drush cex to export active DB config to ../config/default/ (Acquia's VCS dir) and drush cim to import from there to DB.acquia+2
Always use Drush site aliases (@mysite.dev) for remote execution over SSH, as direct server access is via Cloud Next SSH.acquia+1
Export Config (drush cex)
Export from the authoritative environment (usually dev/DDEV) after UI changes:
# Local/DDEV
ddev drush cex -y
# Remote Acquia (via alias)
drush @mysite.dev cex -y
Writes
config/default/*.ymlfrom DBconfigtable rows.drush+1-yskips prompts; add--previewfirst to review diffs.[drush]Then commit/push:
git add config/default/ && git commit -m "Views tweak" && git push.acquia+1
Acquia expects ../config/default/ (not Drupal's default ../config/sync/) for Git-based pipelines.[docs.acquia]
Import Config (drush cim)
Import after code deploy (manual SSH or pipeline hooks) to sync Git YAML → DB:
# Remote Acquia
drush @mysite.dev cim -y
# With status check first
drush @mysite.dev cst # Shows diffs
drush @mysite.dev cim -y
Overwrites DB
configtable with YAML from../config/default/.acquia+1Run in post-code deploy hook (
docroot/hooks/deploy/post-code.sh):bash#!/bin/bash drush cim -y --uri=default drush cr-yautomates; use--partialif you want to skip conflicts (rare).[drush]
Acquia-Specific Workflow
1. DDEV/UI change → ddev drush cex → git commit config/default/ → PR
2. CI tests → merge → git push (triggers Acquia deploy)
3. Acquia deploy: code lands → post-code hook runs drush cim → cache rebuild
4. Verify: drush @mysite.prod cst (should be clean)
Status check (drush cst) is your drift detector—run before/after every cim.[docs.acquia]
Pro tip: With Config Split (from earlier), cex/cim automatically includes the activated split for that env via settings.php. No extra flags needed.mikemadison+1
Best practices for Drupal config sync on Acquia Cloud Next center on treating Git YAML (../config/default/) as the single source of truth, exporting only from dev/DDEV, and automating imports via deploy hooks.acquia+1
Use Config Split + settings.php for env differences and strict CI checks to block drift.acquia+1
Single Authoritative Environment
Always make config changes only on dev/DDEV (never stage/prod), then drush cex → git commit → deploy.specbee+2
Prod/stage get read-only config from Git via
cimin hooks.[docs.acquia]One-way flow: dev → git → all other envs.[specbee]
Git Directory Setup
Acquia standard: ../config/default/ (set in settings.php):
$settings['config_sync_directory'] = '../config/default'; Commit entire directory—every YAML file is code.acquia+1
Workflow Steps
Dev change (views, webforms): UI →
drush cex -y→git commit config/default/ "Updated view XYZ"→ PR.[docs.acquia]CI validates:
drush cst(no diffs),cim, tests (Behat),cex(git clean). Fail PRs with uncommitted config.[acquia]Deploy: Git push → Acquia runs post-code hook
drush cim -y; drush cr.acquia+1Verify:
drush @mysite.prod cstshows clean (no drift).[docs.acquia]
Essential Modules + Patterns
settings.php auto-activates splits (from earlier example).[docs.acquia]
Deploy Hook Template
docroot/hooks/deploy/post-code.sh (chmod +x):
#!/bin/bash drush cim -y --uri=default
drush cr
drush purge:invalidate everything # Varnish [web:26] Drift Prevention Checklist
Never
cimmanually on prod except emergencies (thencex+ commit).[docs.acquia]Block UI config changes on prod/stage (permissions or workflow).[acquia]
Pre-deploy:
drush cston target shows clean.[docs.acquia]Post-deploy: Same check + smoke tests.[acquia]
Nightly audit: Script runs
cston prod, Slack alerts on diffs.[configu]DB syncs (dev←prod): Always
cimafter to restore Git config.[docs.acquia]
Module/Core Updates
Critical gotcha—always cex after composer update:
1. composer update drupal/module
2. drush updb -y
3. drush cex -y # Capture schema changes
4. git commit
Missing step 3 = broken cim on next deploy.[docs.acquia]
Your Setup (DDEV → Cloud Next)
DDEV (local) → drush cex → git/PR → Acquia dev → stage → prod
↓
post-code: cim + varnish purge
Prod stays pristine because nothing touches its DB config except automated Git→DB imports. Matches your Behat/Varnish workflow perfectly.History+1
Common pitfalls in Drupal config management across Acquia environments include forgetting to export after changes, making direct edits on prod/stage, and mishandling env-specific differences.acquia+1
These lead to broken deploys, overwritten work, or drift where prod diverges silently from Git.
Forgot to Export (Most Common)
Pitfall: UI change on dev → deploy code → cim fails because config/default/ lacks the new YAML.
Fix: Always drush cex -y + commit after UI changes. Make it a Git hook or alias: gcx="git add config/default/ && git commit -m && drush cex".acquia+1
Direct Prod/Stage Edits
Pitfall: Someone tweaks a view on prod → next cim from Git overwrites it. Chaos ensues.
Fix: Lock UI config pages on prod/stage (permissions). Train: "UI changes only on DDEV/dev." Post-deploy cst verifies clean.acquia+1
Missing cex After Module/Core Updates
Pitfall: composer update drupal/views adds schema → cim fails on deploy with "unknown config keys."
Fix: Ritual: composer update → drush updb → drush cex → commit.[docs.acquia]
Wrong Sync Directory
Pitfall: Using Drupal default ../config/sync/ instead of Acquia's ../config/default/.
Fix: Set $settings['config_sync_directory'] = '../config/default'; in settings.php. Verify: drush cst shows correct path.acquia+1
Config Split Misconfiguration
Pitfall: Split activates wrong env (dev gets prod caches) → performance disasters.
Fix: Test splits locally: drush gcs local; drush cex; drush cst. Verify settings.php $acquia_env logic matches Acquia vars.acquia+1
DB Sync Without Config Reset
Pitfall: Copy prod DB to dev → prod-only config breaks dev (no devel split).
Fix: After DB import: drush cim -y to restore Git baseline + activate dev split.[docs.acquia]
Uncommitted Splits or Ignored Config
Pitfall: config/split/prod/ or config_ignore.settings not in Git → CI deploys partial config.
Fix: git add config/ recursively. CI job: drush cex && git diff --exit-code config/ (fails dirty).[acquia]
Varnish/CDN Stale After cim
Pitfall: Config changes (new field) not visible due to cached pages.
Fix: Hook: drush p:invalidate everything after cim (your earlier question).[docs.acquia]
Team Workflow Gaps
Pitfall: Junior devs skip cex or edit YAML by hand → merge conflicts, broken imports.
Fix:
PR template: "Did you
drush cex -y?"Pre-commit hook checks
drush cst --format=json | jq '.out_of_sync' | grep -q 0Single dev env as "config authority."[acquia]
Quick Audit Commands
Run these weekly to catch drift:
drush @mysite.prod cst # Shows uncommitted changes
drush @mysite.prod cex --preview # Dry-run export diffs
git diff HEAD~1 config/default/ # Last deploy changed what?
Root cause of 90%: Treating config like "data" instead of code. Enforce Git→DB→verify everywhere.acquia+1
Drupal configuration drift happens when the active DB config (config table) diverges from your Git YAML baseline (../config/default/), often from uncommitted UI changes or manual edits.tothenew+1
Detect it with drush cst and prevent it through CI/CD enforcement, hooks, and splits tailored to your Acquia/DDEV workflow.
Detection Methods
Primary command: drush cst (config status)
drush @mysite.prod cst # Lists out-of-sync items
drush @mysite.prod cst --format=json | jq # Scriptable output
Shows exactly what config in DB differs from Git YAML (e.g., views.view.frontpage). Clean = no drift.[docs.acquia]
Other checks:
drush cex --preview(dry-run export diffs)drush cim --preview(dry-run import conflicts)UI:
/admin/config/development/configuration[tothenew]
Prevention Strategies
1. Git as Source of Truth (Workflow)
Export only from dev/DDEV: UI change →
drush cex → git commit.Never edit config directly on stage/prod—
cimwill overwrite.acquia+1Single direction: dev → git → all envs.
2. CI/CD Pipeline Gates
Every PR/deploy runs:
drush cst --format=json | jq '.out_of_sync' | grep -q 0 || exit 1 # Fail if dirty
drush cim --yes
drush cex --yes
git diff --exit-code config/default/ # Fail if config changed
Catches "forgot to cex" before merge.[configu]
3. Acquia Deploy Hooks (Automation)
docroot/hooks/deploy/post-code.sh:
#!/bin/bash drush cim -y
drush cr
drush p:invalidate everything # Your Varnish drush cst --format=json | jq '.out_of_sync' | grep -q 0 || echo "DRIFT DETECTED" Runs automatically on every code deploy.acquia+1
4. Config Split for Env Differences
Prevents "prod-only" config leaking:
config/default/ # 99% shared
config/split/prod/ # Prod-only (SES, caches)
config/split/dev/ # Devel modules
settings.php activates correct split per $AH_SITE_ENVIRONMENT.acquia+1
5. Prod Lockdown
Permissions: Remove "Administer site configuration" from prod roles.
Audit script (cron/nightly):
bashdrush @mysite.prod cst --format=json | jq > /tmp/prod-config-drift.json # Slack/email if out_of_sync > 0
6. Post-DB-Sync Reset
After dev DB ← prod DB copy:
drush cim -y # Restore Git baseline + dev split
Prevents prod config breaking dev tools.[docs.acquia]
Daily Commands for Your Workflow
# Before feature work
ddev drush cst # Start clean
# After UI change
ddev drush cex -y
git add config/default/
git diff --name-only config/default/ | wc -l # Verify changes
# Pre-PR/deploy
drush cst # Must be 0
# Post-deploy verification
drush @mysite.prod cst
Drift Recovery
If cst shows differences:
1. drush cex --preview # See what you'd overwrite
2. Decide: commit the drift? → drush cex → git commit
3. Or reset: drush cim -y # Force Git baseline
90% of drift = human error. Automate checks, train "cex after every UI change," and your Acquia + DDEV + Behat setup stays pristine.acquia+1
Drupal’s configuration management is basically “all config lives in YAML in Git, and you deliberately push that into databases,” and your process design is what keeps environments from drifting.chromatichq+3
Core concepts (mental model)
Two config states: active config in the DB vs exported config in the filesystem under
$settings['config_sync_directory'](usually../config/sync).tothenew+2Config is things like content types, fields, views, permissions, and module settings, not content entities.umn+1
The only “source of truth” that scales is the exported YAML in Git, not whatever happens to be in prod’s DB today.chromatichq+2
Version control practices
Always commit the entire
config/syncdirectory to Git and treat it as first‑class code.acquia+3Every feature branch that changes behavior should include both PHP/Twig and the corresponding YAML changes in the same commit series.pantheon+1
Use normal Git practices for config: code review, diffs on YAML, small focused changes, and avoid long‑lived branches that accumulate conflicting config.configu+1
Never hand‑edit YAML in Git except to resolve conflicts; the normal flow is change via UI, export, commit.specbee+1
Environment workflow (to avoid chaos)
High‑level “golden path”:
Make config changes only on a canonical dev environment (local or shared).umn+2
Export:
drush cex(or UI) writes the active DB config to YAML inconfig/sync.drupal+1Commit and push the YAML to Git; CI runs tests.linkedin+2
Deploy the code (with YAML) to higher envs, then run
drush cimthere to import into the DB.tothenew+2
Key guardrails to prevent drift:
Never do “drive‑by” config edits directly on staging/prod; if someone does, that change will be overwritten on the next
cim.linkedin+2Lock down permissions so only automation (or ops) can run
cimon prod and only a few people can change config on the canonical dev.acquia+1Before each deploy, check
drush cst(config status) or the UI diff to ensure the target environment has no unexpected local changes.specbee+2
Handling environment differences
You still need some config to vary (e.g., dev mail catchers, logging, test modules). The pattern is: make those differences explicit instead of letting them become random drift.
Use modules like Config Split to have separate splits for
dev,stage,prod, each with its own directory and inclusion rules.chromatichq+1Keep environment selection in code/settings (e.g.
settings.phpenabling the right split based on env vars), not in the UI.tothenew+1Put truly environment‑local things in settings, not config (e.g. DB creds, API keys, some performance flags), so they’re outside the config sync system entirely.pantheon+1
Ops/CI perspective against config drift
From an ops standpoint, you’re trying to enforce “only Git‑driven config is allowed”:
CI/CD pipelines deploy code then automatically run
drush cim --yeson the target env as a step, with smoke tests afterwards.configu+2Pipelines fail if there is uncommitted config (e.g., a job that runs
drush cexand checksgit statusclean) so no one can sneak UI‑only changes.pantheon+1Regularly re‑apply the Git baseline (e.g., nightly
cimdry‑run or diff) to detect and correct any manual tweaks that snuck into prod.cloud.google+1
A practical high‑level rule set that avoids chaos:
One canonical “authoritative” environment for new config, everything exported from there.umn+1
All config stored in Git, reviewed, and deployed via CI/CD.linkedin+2
No direct config edits on prod; imports there only from the main branch’s YAML.acquia+2
Use explicit tools (Config Split, settings.php) for environment‑specific differences, not ad‑hoc changes in UIs.chromatichq+2
If you want, I can sketch a concrete Acquia‑style workflow with settings.php, config_split, branch strategy, and “who is allowed to run cim where” tuned to your DDEV → Cloud Next setup.
Recent content
-
6 hours 54 minutes ago
-
1 day 5 hours ago
-
1 day 9 hours ago
-
2 days 1 hour ago
-
5 days 10 hours ago
-
1 week 2 days ago
-
1 week 4 days ago
-
1 week 4 days ago
-
1 week 4 days ago
-
1 week 4 days ago