devtestprodpost-code-deploy cloud hook of Cloud Platform runs Drush commands, such as updatedb, config:import, and cache:rebuild, on the server.Before you configure an external CI/CD pipeline, ensure that you have the following requirements:
dev, test, and prod environments. You must have a user account with SSH push and API token generation permissions.docroot/ structure with drush/drush in composer.json.You must complete four setup steps before you configure a CI/CD provider.
To generate an Acquia API Token:
In the Cloud Platform user interface, navigate to Account Settings and select API Tokens. For more information, refer to Cloud Platform API v2 authentication.
Click Create Token. Enter a descriptive label, for example, External CI/CD Token.
Copy the API Key and API Secret.
Authenticate the local Acquia CLI session with the following command:
acli ssh-key:create-upload \
--filename=acquia_ci_cd \
--label='External CI/CD' \
--no-interactionTo create and register a dedicated CI SSH Key:
Generate a dedicated SSH key pair for automated deployments and register the public key with Cloud Platform with the following command:
acli ssh-key:create-upload \
--filename=acquia_ci_cd \
--label='External CI/CD' \
--no-interactionThis command generates the following two files in ~/.ssh/.
acquia_ci_cd file is the private key. You must add this key as a CI secret in Step 5. acquia_ci_cd.pub file is the public key. Cloud Platform uploads and registers this key.To retrieve application and environment IDs:
Gather the following values from the Cloud Platform UI:
| Value | Location |
|---|---|
| Application UUID | The last part of the application URL in the Cloud Platform user interface. Refer to Obtaining your application ID. |
| Acquia Git URL | Select your application. Click Actions > View Git Information. Copy the value in the URL field. Refer to Checking out a local copy of your code. |
Environment IDs for dev, test, and prod | Open each environment in the Cloud Platform user interface. The ID is the last part of the URL ([https://cloud.acquia.com/a/environments/](https://cloud.acquia.com/a/environments/)<environmentId>). It appears like this, 12345-abcdef01-2345-6789-abcd-ef0123456789. |
The Git URL has the form <sitename>@svn-XXXXX.prod.hosting.acquia.com:<sitename>.git.
Cloud Platform runs scripts from your repository's hooks/ directory at defined points in the deployment lifecycle. Cloud hooks run automated tasks on the server after you deploy code. Create a hook script at hooks/common/post-code-deploy/drupal-deploy.sh. To add the post-code-deploy cloud hook:
#!/bin/sh
#
# Cloud Platform post-code-deploy hook: runs Drupal deployment operations
# after code is deployed to any environment.
#
# Arguments supplied by Cloud Platform:
# $1 = site name, $2 = target environment.
site="$1"
target_env="$2"
set -ev
drush @$site.$target_env updatedb --yes
drush @$site.$target_env config:import --yes
drush @$site.$target_env cache:rebuild
set +vRun the following commands to make the script executable and commit the script to the repository:
chmod +x hooks/common/post-code-deploy/drupal-deploy.sh
git add hooks/
git commit -m "Add post-code-deploy hook for Drupal operations"Because the hook is in the hooks/common/ directory, it runs on every environmenthooks/dev/, hooks/test/, or hooks/prod/ directories instead
Deploy branch names
A built artifact lands on Acquia Git in a dedicated deploy branch. Your source repository stays on GitHub or GitLab. To migrate from Acquia Pipelines, you must maintain the Acquia branch name convention so that the deploy branch matches your existing environment configuration:
| Source branch | Acquia deploy branch |
main | pipelines-build-main |
master | pipelines-build-master |
The prefix must be pipelines-build- in plural form. A singular prefix of pipeline-build- creates a different branch that does not match previous Acquia Pipelines deployments.
Prerequisite: Complete Part 1.
Navigate to the repository.
Select Settings.
Select Secrets and variables.
Select Actions.
Select New repository secret.
Add all secrets at the repository level instead of the environment level. Every target environment uses the same Git remote and Application Programming Interface, known as API, credentials.
| Secret name | Required | Value |
|---|---|---|
ACQUIA_CLI_KEY | Yes | API token key from Step 1 |
ACQUIA_CLI_SECRET | Yes | API token secret from Step 1 |
ACQUIA_SSH_PRIVATE_KEY | Yes | Full contents of ~/.ssh/acquia_ci_cd, including the BEGIN/END lines |
ACQUIA_SSH_PASSPHRASE | No | Passphrase of the key— omit this secret, if you have not set the passphrase |
ACQUIA_GIT_URL | Yes | Acquia Git URL from Step 3 |
ACQUIA_DEV_ENV_ID | Yes | dev environment ID from Step 3 |
ACQUIA_TEST_ENV_ID | Yes | test environment ID from Step 3 |
ACQUIA_PROD_ENV_ID | Yes | prod environment ID from Step 3 |
To set the secrets from the command line instead of the UI, run the following commands:
gh secret set ACQUIA_SSH_PRIVATE_KEY < ~/.ssh/acquia_ci_cd
gh secret set ACQUIA_CLI_KEY --body "<key>"Add the following workflow as .github/workflows/acquia-deploy.yml or copy the steps into an existing workflow.
The workflow performs the following three actions:
acli push:artifact command runs composer install, removes sensitive files, and commits vendor/ and Drupal scaffold files even though the source repository gitignores them. It pushes the result to the deploy branch and to an immutable tag.dev: The acli api:environments:code-switch command points the environment at that tag. This action triggers the post-code-deploy hook on Cloud Platform.test and prod: Because the tag is immutable, promotion is a code switch with no rebuild. Test and prod receive exactly the bytes validated on dev.name: Deploy to Acquia
on:
push:
branches: [main]
env:
RELEASE_TAG: build-${{ github.run_id }}
jobs:
build-and-deploy-dev:
runs-on: ubuntu-24.04
environment: acquia-dev
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2
# Load the CI SSH key so acli can push to the Acquia Git repository
- uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.ACQUIA_SSH_PRIVATE_KEY }}
ssh-passphrase: ${{ secrets.ACQUIA_SSH_PASSPHRASE }}
- name: Trust the Acquia Git host
run: |
mkdir -p ~/.ssh
GIT_HOST=$(echo "${{ secrets.ACQUIA_GIT_URL }}" | sed 's/.*@//; s/:.*//')
ssh-keyscan -H "$GIT_HOST" >> ~/.ssh/known_hosts
- name: Install and authenticate Acquia CLI
run: |
curl -sSL https://github.com/acquia/cli/releases/latest/download/acli.phar \
-o /usr/local/bin/acli
chmod +x /usr/local/bin/acli
acli auth:login \
--key="${{ secrets.ACQUIA_CLI_KEY }}" \
--secret="${{ secrets.ACQUIA_CLI_SECRET }}" \
--no-interaction
- name: Build and push the artifact to Acquia Git
run: |
acli push:artifact "${{ secrets.ACQUIA_DEV_ENV_ID }}" \
--destination-git-branch="pipelines-build-${{ github.ref_name }}" \
--destination-git-tag="$RELEASE_TAG" \
--no-interaction
# The reference is a positional argument, never --branch=.
# Tags must be prefixed with tags/. --task-wait blocks until the deploy finishes.
- name: Deploy to dev
run: |
acli api:environments:code-switch "${{ secrets.ACQUIA_DEV_ENV_ID }}" \
"tags/$RELEASE_TAG" --task-wait --no-interaction
deploy-test:
needs: build-and-deploy-dev
runs-on: ubuntu-24.04
# Add required reviewers to this GitHub Environment to gate the job on approval
environment: acquia-test
steps:
- name: Install and authenticate Acquia CLI
run: |
curl -sSL https://github.com/acquia/cli/releases/latest/download/acli.phar \
-o /usr/local/bin/acli
chmod +x /usr/local/bin/acli
acli auth:login \
--key="${{ secrets.ACQUIA_CLI_KEY }}" \
--secret="${{ secrets.ACQUIA_CLI_SECRET }}" \
--no-interaction
- name: Promote the release to test
run: |
acli api:environments:code-switch "${{ secrets.ACQUIA_TEST_ENV_ID }}" \
"tags/$RELEASE_TAG" --task-wait --no-interaction
deploy-prod:
needs: deploy-test
runs-on: ubuntu-24.04
environment: acquia-prod
steps:
- name: Install and authenticate Acquia CLI
run: |
curl -sSL https://github.com/acquia/cli/releases/latest/download/acli.phar \
-o /usr/local/bin/acli
chmod +x /usr/local/bin/acli
acli auth:login \
--key="${{ secrets.ACQUIA_CLI_KEY }}" \
--secret="${{ secrets.ACQUIA_CLI_SECRET }}" \
--no-interaction
- name: Promote the release to production
run: |
acli api:environments:code-switch "${{ secrets.ACQUIA_PROD_ENV_ID }}" \
"tags/$RELEASE_TAG" --task-wait --no-interactionTo run existing tests first, add a needs: requirement on the Continuous Integration, known as CI, job. If the theme compiles front-end assets, register the build as a post-install-cmd script in composer.json. The acli push:artifact command runs Composer scripts as part of the build. Therefore, no additional pipeline step is required.
Prerequisite: Complete Part 1.
Step 5: Add CI/CD variables
Add the following variables. Mark each one Masked, and Protected if the default branch is protected.
| Variable name | Required | Value |
|---|---|---|
ACQUIA_CLI_KEY | Yes | API token key from Step 1 |
ACQUIA_CLI_SECRET | Yes | API token secret from Step 1 |
ACQUIA_SSH_PRIVATE_KEY | Yes | Full contents of ~/.ssh/acquia_ci_cd, including the BEGIN/END lines |
ACQUIA_GIT_URL | Yes | Acquia Git URL from Step 3 |
ACQUIA_DEV_ENV_ID | Yes | dev environment ID from Step 3 |
ACQUIA_TEST_ENV_ID | Yes | test environment ID from Step 3 |
ACQUIA_PROD_ENV_ID | Yes | prod environment ID from Step 3 |
To set the variables from the command line instead of the UI, execute the following command:
glab variable set ACQUIA_CLI_KEY "<key>" --maskedIf acli auth:login fails only on some branches, verify whether the variables are marked Protected while the branch is not.
Add the following jobs to .gitlab-ci.yml, alongside existing test and quality stages.
The jobs perform the following three actions:
acli push:artifact command runs composer install, removes sensitive files, and commits vendor/ and Drupal scaffold files even though the source repository ignores them. It pushes the result to the deploy branch and to an immutable tag.acli api:environments:code-switch command points the environment at that tag. This action triggers the post-code-deploy hook on Cloud Platform.test and prod. Because the tag is immutable, promotion is a code switch with no rebuild. Test and prod receive exactly the bytes validated on dev.stages:
- build
- deploy-dev
- deploy-test
- deploy-prod
default:
image: php:8.3-cli
variables:
DEPLOY_BRANCH: "pipelines-build-${CI_DEFAULT_BRANCH}"
.acquia-setup: &acquia-setup
- apt-get update -y && apt-get install -y git unzip curl openssh-client
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- curl -sSL https://github.com/acquia/cli/releases/latest/download/acli.phar -o /usr/local/bin/acli
- chmod +x /usr/local/bin/acli
- acli auth:login --key="$ACQUIA_CLI_KEY" --secret="$ACQUIA_CLI_SECRET" --no-interaction
build-push:
stage: build
script:
- *acquia-setup
# Load the CI SSH key so acli can push to the Acquia Git repository
- eval $(ssh-agent -s)
- echo "$ACQUIA_SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- GIT_HOST=$(echo "$ACQUIA_GIT_URL" | sed 's/.*@//; s/:.*//')
- ssh-keyscan -H "$GIT_HOST" >> ~/.ssh/known_hosts
- RELEASE_TAG="build-${CI_PIPELINE_ID}"
- >
acli push:artifact "$ACQUIA_DEV_ENV_ID"
--destination-git-branch="$DEPLOY_BRANCH"
--destination-git-tag="$RELEASE_TAG"
--no-interaction
- echo "RELEASE_TAG=${RELEASE_TAG}" >> build.env
artifacts:
reports:
dotenv: build.env
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# The reference is a positional argument, never --branch=.
# Tags must be prefixed with tags/. --task-wait blocks until the deploy finishes.
.deploy: &deploy
script:
- *acquia-setup
- acli api:environments:code-switch "$TARGET_ENV_ID" "tags/${RELEASE_TAG}" --task-wait --no-interaction
deploy-dev:
<<: *deploy
stage: deploy-dev
variables:
TARGET_ENV_ID: "$ACQUIA_DEV_ENV_ID"
environment:
name: acquia-dev
needs: [build-push]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
deploy-test:
<<: *deploy
stage: deploy-test
variables:
TARGET_ENV_ID: "$ACQUIA_TEST_ENV_ID"
environment:
name: acquia-test
needs: [build-push, deploy-dev]
rules:
# when: manual holds the job until a team member starts it
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
deploy-prod:
<<: *deploy
stage: deploy-prod
variables:
TARGET_ENV_ID: "$ACQUIA_PROD_ENV_ID"
environment:
name: acquia-prod
needs: [build-push, deploy-test]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manualIf the theme compiles front-end assets, register the build as a post-install-cmd script in composer.json. The acli push:artifact command runs Composer scripts as part of the build. Therefore, no additional pipeline step is required.
Acquia publishes an Agent Skill that performs this entire setup through an Artificial Intelligence, known as AI, coding agent such as Claude Code, GitHub Copilot, Cursor, Gemini command-line tool, known as CLI, or OpenAI Codex. The skill is playbooks/setup-external-ci-cd from the acquia/acquia-skills repository.
Run the following command in the project directory. The npx skills CLI detects the active AI tool and installs the skill files in the correct location:
npx skills add acquia/acquia-skillsTo install the playbook skills only, run the following command:
npx skills add acquia/acquia-skills/skills/playbooksAfter you install the skill, ask the agent in plain language. For example, set up an external CI/CD with GitHub Actions for my Acquia application so it pushes to main build and deploy to dev, then promotes the same release to test and prod.
The agent performs the following actions:
acli is installed and authenticated, and runs acli auth:login if necessary.dev, test, and prod environment IDs.post-code-deploy hook into the repository.gh) or GitLab CLI (glab).Review the changes it made before you commit:
git status
git add .github hooks # or .gitlab-ci.yml hooks
git commit -m "Add Acquia CI/CD configuration"Update the skill with the npx skills update acquia/acquia-skills command.
Acquia supports the integration surface between the CI/CD tool and Cloud Platform. This includes SSH and Git access, Acquia CLI, Cloud Platform API, and Cloud Platform hooks. You and the external CI/CD provider own the configuration, availability, and behavior of the external CI/CD provider. Teams without an existing CI/CD toolchain must use built-in tooling, such as Code Studio, as a starting point. Migrate to external tooling later with this guide.
If this content did not answer your questions, try searching or contacting our support team for further assistance.
devtestproddocroot/ structure with drush/drush in composer.json.If this content did not answer your questions, try searching or contacting our support team for further assistance.