The code workflow
Goal: know the full code workflow on the Acquia side: where code lives, how it reaches production, how data moves between environments, and how to run Drush against them.
The workflow is the same whether your site renders in-platform or serves a headless frontend. For writing the Drupal application itself (modules, hooks, theming), see drupal.org/docs; for configuring or administering the subscription, see docs.acquia.com.
What you’ll have when you’re done
Section titled “What you’ll have when you’re done”- A repository laid out the way Cloud Platform expects, and the reason
docroot/is non-negotiable. - A working model of code flow: branches on dev, tags on prod, push-to-deploy in between.
- Databases and files moving between environments in both directions.
- Drush running against any environment from your terminal.
Prerequisites
Section titled “Prerequisites”- The deploy loop from the code-workflow quickstart (you’ve pushed at least once)
- acli authenticated (CLI quickstart)
-
Lay the repository out the way Cloud Platform expects
Section titled “Lay the repository out the way Cloud Platform expects”myapp/├── composer.json # Drupal + dependencies (Composer)├── docroot/ # the webroot Cloud Platform uses├── config/ # exported Drupal configuration└── hooks/ # Cloud Hooks: run on deploy eventsdocroot/is the fixed contract: Cloud Platform serves from it, so Drupal’sindex.phpmust live there (projects usingweb/rename or symlink; set Composer’s installer paths todocroot/from the start).hooks/is optional but is where post-deploy automation like cache rebuilds belongs.For a repository with no codebase in it yet, Set up a codebase scaffolds one in exactly this layout and pushes it.
-
Move code forward: branches to Dev and Stage, tags to Prod
Section titled “Move code forward: branches to Dev and Stage, tags to Prod”Each environment tracks one branch or tag, shown on its environment card. The card shows the environment’s label, while commands, aliases, and
AH_SITE_ENVIRONMENTtake its name. The working convention, label and name together:- Dev (
dev) tracks a branch: pushing to it deploys it (the quickstart’s loop). - Stage (
test) tracks a branch you promote into when dev looks right. - Prod (
prod) tracks a tag. Cut one and switch to it, so production state is immutable and rollback is “switch back to the previous tag”:
Terminal window git tag 2026-07-02git push origin 2026-07-02Those two commands run in a clone of the application’s Acquia repository (the quickstart’s checkout), where the deployed branch already holds built code. On the artifact flow from set up a codebase, your source repository’s commits never reach the platform, so a tag on them deploys nothing. Cut the tag from a build instead:
acli push:artifact --destination-git-branch=<deployed-branch> --destination-git-tag=2026-07-02builds the artifact on the named artifact branch and pushes it as that tag (see push:artifact).Switching an environment onto that tag is one command:
Terminal window acli api:environments:code-switch myapp.prod "tags/2026-07-02" --task-waitA tag has to be prefixed with
tags/, which is the one thing about this command that is easy to get wrong; a branch name is passed bare (acli api:environments:code-switch myapp.dev my-feature-branch).--task-waitholds the terminal until the deploy task settles and exits non-zero if it failed, which is what makes the command usable in a script.Switch codeon the environment card in the Cloud Platform user interface does the same thing.On production, switching code is a destructive-grade permission either way: restrict who holds it through Cloud Platform roles, which separate pulling and deploying code on production from the same operations on non-production (see About Cloud Platform permissions on docs.acquia.com).
- Dev (
-
Move data backward: databases and files flow down, not up
Section titled “Move data backward: databases and files flow down, not up”Code flows dev → prod; data flows prod → dev. Copy a database or the files directory between environments with
acli api:environments:database-copy myapp.dev my_db <source-env-id>andacli api:environments:file-copy myapp.dev --source=<source-env-id>; both queue a task that--task-waitblocks on. To bring production data to your machine instead:Terminal window acli pull:db myapp.prodacli pull:files myapp.prodCopying a database overwrites the target: copying prod to dev destroys whatever dev’s database held. That’s the routine direction precisely because dev data is disposable; never copy dev over prod. Like production
Switch code, database copies are role-gated: grant them deliberately, because one misdirected copy is a restore-from-backup incident.The two directions run opposite each other, and a deploy fires the Cloud Hooks covered below:
-
Run Drush against an environment
Section titled “Run Drush against an environment”acliproxies Drush to any environment, no SSH setup beyond your registered key:Terminal window acli remote:drush myapp.dev -- statusacli remote:drush myapp.dev -- crEnvironments that harden PHP by disabling process execution reject
remote:drushwithpcntl_exec(): ... Permission denied(the Drush launcher can’t hand off to the site’s Drush). On those, run the site’sdrush.phpthrough PHP over SSH instead, the same invocation the Cloud Hooks below use:Terminal window acli remote:ssh myapp.dev -- \'php /var/www/html/vendor/drush/drush/drush.php \--root=/var/www/html/docroot status'For anything more interactive, open a shell on the environment:
Terminal window acli ssh myapp.devFor what to run with Drush from here, start with manage configuration: configuration imports and update hooks are the commands you’ll run against an environment most often, and that page covers the order they belong in.
-
Automate the loop when it stabilizes
Section titled “Automate the loop when it stabilizes”Once pushing, tagging, and copying data are routine, move them into CI/CD: Pipelines builds and tests your application on every push and commits the result as a deployable artifact branch. Start at the Pipelines quickstart; teams already on Code Studio stay on it (see Automate with CI/CD). For scripted environment operations outside CI (nightly database refreshes, environment creation), use the Cloud Platform API.
Automate deploy events with Cloud Hooks
Section titled “Automate deploy events with Cloud Hooks”The hooks/ directory from step 1 holds Cloud Hooks, Cloud Platform’s deploy-event mechanism: shell scripts that run on the environment when code or data moves, so “deploy code, then run updates” happens as one unit instead of a checklist. Layout:
hooks/├── common/ # every environment│ └── post-code-update/ # on new commits│ └── update-db.sh├── dev/ # only the dev environment└── prod/ # only the prod environmentThe first directory level picks the environment (common for all); the second names the event. The deploy loop runs on two of them: post-code-update (new commits landed on the tracked branch) and post-code-deploy (the environment switched branch or tag, i.e. Switch code). Scripts in an event directory run in alphabetical order; a non-zero exit marks the deploy task failed, which is what you want when updates didn’t run.
For the full event list, each event’s arguments, and the exact failure semantics, see Supported hooks in the acquia/cloud-hooks repository; for the Cloud Next limits on hooks (memory, timeout, process count), see Automating with Cloud Hooks on docs.acquia.com.
The worked example pairs every code change with the update path from the troubleshooting entry below. The code events pass the site name and the target environment as their first two arguments; the six-argument contract has the rest. On a push to a tracked branch, the third and fourth arguments carry the same value. Data-only operations fire their own hooks (post-db-copy, post-files-copy), so a code hook doesn’t need to guard against them:
#!/bin/shsite="$1"target_env="$2"
drush @$site.$target_env deploy --yesMake it executable (chmod +x) before committing, and place the same script in post-code-deploy/ so tag switches on production get the identical treatment. drush deploy is a single Drush command that runs the post-deploy sequence in the order that works: database updates, then the configuration import, then a cache rebuild. Running those steps by hand in the wrong order is a common way to make a deploy fail; manage configuration covers the sequence and the two contrib modules that keep an unconditional import from doing damage. The hook mechanism that triggers all of it is the Acquia side.
Hook scripts are committed code, not a secret store: a credential or key a hook needs belongs in a platform environment variable, never in the script.
One field-tested wrinkle: hooks run as the environment’s web user, and the vendor/bin/drush shim can lack execute permission on the environment’s filesystem. If the alias invocation fails that way, call Drush through PHP directly, resolving the environment’s docroot (/mnt/www/html/<site><env>/docroot; production has no <env> suffix):
php ${DOCROOT}/../vendor/drush/drush/drush.php --root=${DOCROOT} updb -yRoll back a release
Section titled “Roll back a release”Prod tracking a tag makes rollback a switch, not a rebuild, but only the code half is automatic. The precondition that makes this runbook safe: a database backup exists from before the deploy. Cloud Platform backs up every database daily on its own, but a daily backup is kept for only three days, while an on-demand backup stays until you delete it (Taking backups on docs.acquia.com). Take one as part of any risky deploy. For the platform’s own recovery commitments (on Cloud Platform Enterprise and Site Factory, a one-hour recovery point objective for production, snapshot recovery at up to an hour per 50 GB), see availability and disaster recovery on docs.acquia.com.
acli api:environments:database-backup-create myapp.prod my_db --task-waitmy_db is the database’s name, not the environment’s. acli api:environments:database-list myapp.prod prints every database on the environment; the site’s own is the entry whose flags.default is true. The backup runs as a background task, so keep --task-wait: with it the command exits zero only once the backup exists, which is what lets a deploy script refuse to continue without one. The Cloud UI has the same action on the environment; see Taking backups on docs.acquia.com.
Every step below is a command, so the runbook runs from a terminal at 2am by whoever is on call.
-
Identify the previous good tag.
acli app:vcs:info myapp --deployedprints the application’s deployed branches and tags, one row each, with the environment each is deployed to in the last column: that is what production is running right now.git tag --sort=-creatordatein your checkout lists the candidates to go back to. -
Decide whether data must roll back too. The rule: if the bad release ran update hooks or changed configuration on production, the database no longer matches the old code, and code-only rollback can error harder than the bug you’re escaping. The Cloud Hook above makes the answer “yes” for any release that shipped updates. No schema or config changes means code-only is safe.
-
Switch the code back to the previous tag.
Terminal window acli api:environments:code-switch myapp.prod "tags/2026-07-01" --task-waitThe
tags/prefix is required on a tag and wrong on a branch, and this is the step where forgetting it costs the most. -
If step 2 said yes, restore the pre-deploy backup. List the environment’s backups, then restore the one taken before the deploy by its
id:Terminal window acli api:environments:database-backup-list myapp.prod my_dbacli api:environments:database-backup-restore myapp.prod my_db 1234 --task-waitEach entry in the list carries an
id, atype, andstarted_atandcompleted_attimestamps; theidis the third argument to the restore. The backup to restore is the last one whosecompleted_atpredates the deploy, not necessarily the newest. Restoring overwrites the database’s current contents: content edited since the deploy is lost, so say so to the editorial team before, not after. -
Verify: the site renders,
acli remote:drush myapp.prod -- statusis clean, and a spot-check of recent content matches expectations.
Fix forward on a branch and cut a new tag; never reuse or retag the bad one. A headless frontend rolls back the same way its CI deployed it, by switching to or redeploying the last good artifact: see multiple environments and a production gate.
When something goes wrong
Section titled “When something goes wrong”Deploy task succeeds but the site errors after a code switch
code moved, data didn’t. A new module or config expects the database state that matches the code. Run the update path against the environment (acli remote:drush myapp.dev -- updb), and treat “deploy code, then run updates” as one unit; that pairing is what Cloud Hooks automate.
docroot missing / site serves a directory listing
the webroot contract is broken; Composer installed Drupal to web/. Fix the installer paths in composer.json so index.php lands in docroot/.
A database copy wiped work on the target environment
that’s the documented overwrite behavior, and why data only flows toward disposable environments. If a backup of the target from before the copy exists (the platform’s daily, or one taken on purpose), restore it. The rollback runbook has the two commands (api:environments:database-backup-list, then api:environments:database-backup-restore). Take an on-demand backup of the target before the next copy.
Next steps
Section titled “Next steps”- Manage configuration: the sync directory, importing on deploy, and keeping a deploy from reverting production settings.
- Set up local development: the same codebase and data on your machine.
- Automate with CI/CD: the push-to-deploy loop, without you in it.
- Supported hooks in the acquia/cloud-hooks repository: every deploy event, its arguments, and what happens when a hook fails.
- acli command reference: every command this guide used, with flags.
- Call the Cloud Platform API: the REST API behind the UI actions above.
Was this page helpful?
What went wrong?
Still stuck? Contact Acquia Support (opens in a new tab)