Skip to content

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.

  • 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.
  1. 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 events

    docroot/ is the fixed contract: Cloud Platform serves from it, so Drupal’s index.php must live there (projects using web/ rename or symlink; set Composer’s installer paths to docroot/ 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.

  2. 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_ENVIRONMENT take 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-02
    git push origin 2026-07-02

    Those 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-02 builds 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-wait

    A 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-wait holds 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 code on 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).

  3. 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> and acli api:environments:file-copy myapp.dev --source=<source-env-id>; both queue a task that --task-wait blocks on. To bring production data to your machine instead:

    Terminal window
    acli pull:db myapp.prod
    acli pull:files myapp.prod

    Copying 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:

  4. acli proxies Drush to any environment, no SSH setup beyond your registered key:

    Terminal window
    acli remote:drush myapp.dev -- status
    acli remote:drush myapp.dev -- cr

    Environments that harden PHP by disabling process execution reject remote:drush with pcntl_exec(): ... Permission denied (the Drush launcher can’t hand off to the site’s Drush). On those, run the site’s drush.php through 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.dev

    For 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.

  5. 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.

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 environment

The 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:

hooks/common/post-code-update/deploy.sh
#!/bin/sh
site="$1"
target_env="$2"
drush @$site.$target_env deploy --yes

Make 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):

Terminal window
php ${DOCROOT}/../vendor/drush/drush/drush.php --root=${DOCROOT} updb -y

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.

Terminal window
acli api:environments:database-backup-create myapp.prod my_db --task-wait

my_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.

  1. Identify the previous good tag. acli app:vcs:info myapp --deployed prints 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=-creatordate in your checkout lists the candidates to go back to.

  2. 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.

  3. Switch the code back to the previous tag.

    Terminal window
    acli api:environments:code-switch myapp.prod "tags/2026-07-01" --task-wait

    The tags/ prefix is required on a tag and wrong on a branch, and this is the step where forgetting it costs the most.

  4. 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_db
    acli api:environments:database-backup-restore myapp.prod my_db 1234 --task-wait

    Each entry in the list carries an id, a type, and started_at and completed_at timestamps; the id is the third argument to the restore. The backup to restore is the last one whose completed_at predates 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.

  5. Verify: the site renders, acli remote:drush myapp.prod -- status is 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.

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.

Was this page helpful?