# Everyday workflows

**Goal:** do your routine Cloud Platform work (logs, databases, remote commands, deploys) from the terminal without opening the UI.

The jobs below are independent: orient yourself once, then jump to the one you need and stop there. Every command links to its [reference entry](/cloud-platform/reference/acli/) for flags and defaults.

## What you'll have when you're done

- A reliable way to know which [application](/start-here/glossary/#application-cloud-platform) and [environment](/start-here/glossary/#environment) a command will act on, before it acts.
- The highest-frequency jobs (logs, database pulls, remote [Drush](/start-here/glossary/#drush), SSH, [artifact](/start-here/glossary/#build-artifact) deploys, temporary environments) each reduced to one or two commands.
- Non-interactive authentication for scripts and CI, and Composer hook points around any [acli](/start-here/glossary/#acli) command.

## Prerequisites

- [acli installed and authenticated](/cloud-platform/cli/quickstart/)
- Access to at least one Cloud Platform application with a dev environment you're allowed to experiment on

Individual jobs need a little more (a local database server to import into, an SSH key registered with the platform, a `composer.json` at your project root). Each job below names its own before the first command.

:::note[One vocabulary, everywhere]
The words in `acli` output are the Cloud Platform API's own response field names: `uuid`, `name`, `environment`, and `subscription` mean the same thing in CLI output and in API responses. When a term is unfamiliar, the [glossary](/start-here/glossary/) is the canonical definition.
:::

## Know what you're pointed at

Most `acli` commands act on an application or an environment. You can always pass one explicitly as `app-name.env` (for example `myapp.dev`), and you should for anything destructive. The `env` half is the environment's name, not the label on its card, so the environment labeled Stage is `myapp.test`. For day-to-day work in a project checkout, link the directory once:

```bash
acli app:link
```

[`app:link`](/cloud-platform/reference/acli/#applink) writes the application UUID to `.acquia-cli.yml` in the project root; from then on, commands run in that directory default to the linked application ([`app:unlink`](/cloud-platform/reference/acli/#appunlink) removes it). To see every `app.env` alias you can target, run [`acli remote:aliases:list`](/cloud-platform/reference/acli/#remotealiaseslist).

To check which environments an application actually has, ask the API with [`api:applications:environment-list`](/cloud-platform/reference/acli/#the-cloud-platform-api) (one of the generated `api:*` commands) and filter with [`jq`](https://jqlang.org/download/), the standard JSON filter tool. It's a separate install:

<Tabs syncKey="os">
  <TabItem label="macOS">
    ```bash
    brew install jq
    ```
  </TabItem>
  <TabItem label="Linux">
    ```bash
    sudo apt-get install jq
    ```

    On other distributions, `jq` is in the standard package repositories under the same name.
  </TabItem>
</Tabs>

Then filter the environment list to just the names:

```bash
acli api:applications:environment-list myapp | jq '.[].name'
```

```text
"dev"
"prod"
"test"
```

Those names are what you append to the application alias: `myapp.dev`, `myapp.prod`. An application with Continuous Delivery Environments or extra environments (`ra`, `ama`) lists those here too. Make this your pre-flight check: before anything that writes (deploying an artifact, creating or deleting an environment), confirm the alias you're about to pass names the environment you think it does.

## Jobs

- [Tail logs from an environment](#tail-logs-from-an-environment): stream live log output while you reproduce a problem.
- [Pull a database or files](#pull-a-database-or-files): import a remote environment's data into your local stack.
- [Run Drush remotely or open a shell](#run-drush-remotely-or-open-a-shell): run Drush commands or a one-off shell command against an environment, no local Drush install needed.
- [Deploy code as an artifact](#deploy-code-as-an-artifact): push a built artifact to your Acquia git remote and point an environment at it.
- [Temporary environments](#temporary-environments): spin up and tear down a Continuous Delivery Environment, and block on asynchronous API tasks.
- [Hook scripts around any acli command](#hook-scripts-around-any-acli-command): run your own script before or after any `acli` command via Composer.
- [Non-interactive authentication](#non-interactive-authentication): pass credentials as environment variables so CI never waits for a login prompt.

## Tail logs from an environment

Watch an environment's logs live, without opening the Cloud Platform user interface:

```bash
acli app:log:tail myapp.dev
```

[`app:log:tail`](/cloud-platform/reference/acli/#applogtail) prompts you to pick which logs to tail, as a comma-separated list:

- Balancer, Varnish, and Apache requests
- Apache and PHP errors
- Drupal watchdog and requests
- MySQL slow queries

It then streams `Streaming has started` and their output to your terminal as the servers receive new requests. Interact with the site in another window and watch the messages stream in; press Ctrl+C to stop. This is the fastest way to diagnose a live problem without touching the UI.

For which log answers which question, and how to download one instead of streaming it, see [what the platform logs](/cloud-platform/observability/logs/); for symptom-first triage using these logs, see [diagnose a slow or erroring site](/cloud-platform/observability/diagnose/).

## Pull a database or files

Get a remote environment's database, files, or both onto your local machine:

```bash
acli pull:db myapp.prod
```

[`pull:database`](/cloud-platform/reference/acli/#pulldatabase) imports the latest available backup into your local database, so you need a local database server to import into (the [local dev quickstart](/cloud-platform/local-dev/quickstart/) sets one up). To download the dump without importing it, pass `--no-import`. The backup may be up to 24 hours old; if none exists, one is created. To force a fresh backup, see the `--on-demand` flag in the [reference entry](/cloud-platform/reference/acli/#pulldatabase).

After the import, `acli` runs `drush sql-sanitize`, which scrambles user emails and passwords, so a production pull does not leave real credentials on your laptop. Two things switch it off: `--no-scripts` skips it explicitly, and `--no-import` implies `--no-scripts`, so a download-only pull never sanitizes either.

That second case covers `ddev pull acquia`: DDEV's Acquia provider downloads with `--no-import` and imports the dump itself, so what lands in your local database is unsanitized. Run `ddev drush sql-sanitize` afterwards if you pulled from production.

The same shape works for Drupal public files with [`pull:files`](/cloud-platform/reference/acli/#pullfiles), and [`pull:all`](/cloud-platform/reference/acli/#pullall) copies code, database, and files in one go. Local database connection settings come from the `ACLI_DB_HOST`, `ACLI_DB_NAME`, `ACLI_DB_USER`, and `ACLI_DB_PASSWORD` environment variables, already set for you in recommended local stacks like Lando (see [configuration](/cloud-platform/reference/acli/#environment-variables)).

## Run Drush remotely or open a shell

Open a shell on an environment, or run a single command and return, over SSH:

```bash
# Open a shell in the environment.
acli remote:ssh myapp.dev

# Run one command and return.
acli remote:ssh myapp.dev -- ls -al
```

[`remote:ssh`](/cloud-platform/reference/acli/#remotessh) needs an SSH key registered with the platform; [`acli ssh-key:create-upload`](/cloud-platform/reference/acli/#ssh-keycreate-upload) does both halves in one step. The shell lands in the environment's site root (`/var/www/html`), with the codebase, `config/`, `docroot/`, and `hooks/` right there.

You don't need Drush installed locally to run it on an environment:

```bash
acli remote:drush myapp.dev -- status --fields=db-status
```

Note the `--` separating [`remote:drush`](/cloud-platform/reference/acli/#remotedrush)'s own arguments from the Drush command and its options; it's required.

On environments that harden PHP by disabling process execution, `remote:drush` (and the platform's `drush` launcher shim) fail with `pcntl_exec(): ... Permission denied`. When that happens, run the site's own `drush.php` through PHP over SSH instead. This is the same invocation the platform's Cloud Hooks use, and it works where the launcher can't:

```bash
acli remote:ssh myapp.dev -- \
  '/usr/local/php8.4/bin/php \
   /var/www/html/vendor/drush/drush/drush.php \
   --root=/var/www/html/docroot status'
```

Point `--root` at the environment's `docroot/`; swap `status` for any Drush command. For where `drush` itself fits next to `acli`, see [Which CLI do I use?](/cloud-platform/cli/#which-cli-do-i-use).

## Deploy code as an artifact

Cloud Platform environments run built code (vendor directories included) while your source repository typically ignores them. [`push:artifact`](/cloud-platform/reference/acli/#pushartifact) bridges that gap. The artifact it commits includes vendor directories and scaffold files, even if they are gitignored in your source. From a project with a `composer.json` at its root, it runs `composer install`, removes sensitive files, commits the built result, and pushes it to your Acquia git remote:

```bash
acli push:artifact --destination-git-branch=main-build
```

The push alone deploys nothing: an environment picks up `main-build` only once it tracks that branch, so point one at it with `acli api:environments:code-switch myapp.dev main-build --task-wait`. A push to a branch no environment tracks succeeds silently and changes nothing anywhere.

To run extra build steps such as `npm install` inside the artifact build, add a `post-install-cmd` script to your `composer.json`. The [reference entry](/cloud-platform/reference/acli/#pushartifact) has the details and the `ACLI_PUSH_ARTIFACT_*` environment variables for CI use; to run this deploy from CI instead of your terminal, see the [CI/CD guide](/cloud-platform/ci-cd/guide/).

## Temporary environments

Spin up a [Continuous Delivery Environment](/start-here/glossary/#cde) (CDE) for a branch, and tear it down when you're done:

```bash
acli env:create pr-123 feature/my-branch
acli env:delete
```

See [`env:create`](/cloud-platform/reference/acli/#envcreate) and [`env:delete`](/cloud-platform/reference/acli/#envdelete).

Many platform operations are asynchronous: the API returns immediately and the work happens in the background. [`app:task-wait`](/cloud-platform/reference/acli/#apptask-wait) blocks until a task finishes, which is what makes commands like [`api:environments:database-backup-create`](/cloud-platform/reference/acli/#the-cloud-platform-api) scriptable:

```bash
# my_db: your database name, from acli api:environments:database-list myapp.dev
BACKUP_TASK=$(acli api:environments:database-backup-create \
  myapp.dev my_db)
# task-wait reads the task ID from the JSON the previous command printed
acli app:task-wait "$BACKUP_TASK"
```

For when a backup is worth taking on purpose and how to restore one, see the [code-workflow guide's rollback runbook](/cloud-platform/code-workflow/guide/#roll-back-a-release).

## Hook scripts around any acli command

To run your own script before or after any `acli` command, add a Composer script named `pre-acli-<command>` or `post-acli-<command>` (command name with dashes instead of colons) to your project's root `composer.json`:

```json
"scripts": {
   "pre-acli-pull-database": [
       "echo \"I'm pulling the database now!\""
   ],
   "post-acli-pull-database": [
       "echo \"I'm done pulling the database!\""
   ]
}
```

The naming pattern is `(pre|post)-acli-(command name with dashes)`: `pre-acli-push-database`, `post-acli-pull-files`, and so on. A misnamed script silently never runs, so copy the command name exactly.

Use the command's **canonical** name, not an alias. `acli pull:db` is an alias of `pull:database`, so the hook is `pre-acli-pull-database`; it fires whether you type `acli pull:db` or `acli pull:database`. `pre-acli-pull-db` never runs. Confirm the canonical name with `acli <command> --help` (the first line of the usage block) if you're unsure, and see the [reference](/cloud-platform/reference/acli/#composer-hook-points) for the full hook-point naming convention.

## Non-interactive authentication

`acli` never prompts for login if credentials are already available. It checks, in priority order:

1. An access token in the `ACLI_ACCESS_TOKEN` environment variable, with its corresponding `ACLI_ACCESS_TOKEN_EXPIRY` value.
2. An `ACLI_KEY` environment variable and its corresponding `ACLI_SECRET`.
3. Values stored in `~/.acquia/cloud_api.conf` by [`acli auth:login`](/cloud-platform/reference/acli/#authlogin).

For CI jobs and containers, pass an API token or key/secret pair (generated at [cloud.acquia.com/a/profile/tokens](https://cloud.acquia.com/a/profile/tokens)) as environment variables, and add `-n` (`--no-interaction`) so nothing ever waits for input:

```bash
ACLI_KEY=<key> ACLI_SECRET=<secret> \
  acli -n api:applications:list
```

Because the env vars outrank the config file, this also works on a machine where someone else's credentials are stored. It also means a stale `ACLI_ACCESS_TOKEN` left in your shell wins over a perfectly good config file (see the token-expiry entry below). To put this to work in CI, see the [CI/CD guide](/cloud-platform/ci-cd/guide/).

## When something goes wrong

**`Cloud Platform API returned an error: The access token has expired.` mid-session**: your token expired between commands. Locally, run `acli auth:login` again with a fresh token. In a Cloud IDE, authentication uses refresh tokens tied to your Cloud Platform SSO session, and two IDE browser tabs open at once confuse the token refresh. Keep a single IDE tab open. If you exported `ACLI_ACCESS_TOKEN` in your shell, unset it: it outranks your valid config file (see the [credential precedence order](#non-interactive-authentication)).

**`Your Cloud Platform API credentials are invalid.`**: the stored key/secret was revoked or mistyped. Run `acli auth:login` to reset your API credentials with a freshly generated token.

**The command ran, against the wrong application or environment.** The worst failure produces no error at all. Two habits prevent it: run your pre-flight check from [orientation above](#know-what-youre-pointed-at) (`acli api:applications:environment-list myapp | jq '.[].name'`, or `acli remote:aliases:list`) before anything destructive, and always pass the explicit `app.env` alias to writing commands (`push:database`, `push:files`, `env:delete`) instead of relying on the linked directory. Related error: `Cloud Platform API returned an error: The application you are trying to access does not exist, or you do not have permission to access it.`: either the alias is wrong, or (a documented known issue) the same SSH key is attached to two Cloud Platform accounts with access to the same subscription; give each account its own key.

**A documented command or flag doesn't exist in your `acli`**: version mismatch. Compare `acli --version` against the version the [reference](/cloud-platform/reference/acli/) states at the top, then update with [`self-update`](/cloud-platform/reference/acli/#self-update):

```bash
$ acli self-update
Downloading Acquia CLI (acquia/cli) 4.0.0
Download finished
Updating phar...
Successfully updated acli
```

**`remote:drush` fails with `pcntl_exec(): ... Permission denied`**: the environment restricts PHP process execution, so the Drush launcher can't hand off to the site's Drush. Use the [`php … drush.php` over SSH form](#run-drush-remotely-or-open-a-shell), which bypasses the launcher entirely.

**There's no `acli push:code` command.** Deployment through `acli` is artifact-based, via [`push:artifact`](/cloud-platform/reference/acli/#pushartifact). (A hidden `push:code` stub exists inside Cloud IDEs, but all it does is print a reminder to push code upstream with `git`.) If your source and deployed branches are the same, plain `git push` is the right tool, not `push:artifact`.

**Your Composer hook never runs.** The script name didn't match `(pre|post)-acli-(command name with dashes)` exactly, and a misnamed script fails silently with no error from `acli`. The two usual causes: colons left in place of dashes, or an alias used instead of the canonical command name (`pull-db` instead of `pull-database`). Run `acli <command> --help` and copy the canonical name from the first usage line.

## Next steps

- [acli command reference](/cloud-platform/reference/acli/): every command's flags, defaults, and exit codes.
- [Which CLI do I use?](/cloud-platform/cli/#which-cli-do-i-use): `acli` doesn't manage Pipelines, Drupal, or Canvas components; this table routes you to the tool that does.
