## Custom Actions with DeployHQ

Custom Actions allow you to run arbitrary commands inside Docker containers as part of your deployment pipeline. Instead of transferring files to a remote server, DeployHQ pulls a Docker image, uploads your repository source code into the container, and executes your commands.

This is useful for deployments that rely on CLI tools such as the AWS CLI, Terraform, kubectl, or any custom toolchain packaged in a Docker image.

**Prerequisites:**

* **Commands:** At least one command to execute inside the container

## How It Works

When a deployment runs with a Custom Action server, DeployHQ:

1. Checks out your repository at the target revision
2. Uploads the source code to the build server
3. Pulls the configured Docker image
4. Injects any configuration files (if enabled)
5. Runs each command sequentially inside the container, with your source code mounted at `/data`
6. Records the deployment result

Your commands run with the working directory set to `/data`, where your full repository is available.

## Choosing an Image Source

Custom Actions support two image sources:

### Curated Templates

Select from pre-configured templates for common tools. Each template provides a recommended Docker image and example commands to get you started.

| Template | Image | Use Case |
|----------|-------|----------|
| AWS CLI | `amazon/aws-cli:latest` | Deploy to AWS services (S3 sync, ECS update, Lambda deploy) |
| Google Cloud SDK | `google/cloud-sdk:slim` | Deploy to Google Cloud Platform services |
| Azure CLI | `mcr.microsoft.com/azure-cli:latest` | Deploy to Microsoft Azure services |
| Kubernetes | `bitnami/kubectl:latest` | Apply manifests to Kubernetes clusters |
| Terraform | `hashicorp/terraform:latest` | Manage infrastructure as code |
| Node.js | `node:20-alpine` | Run Node.js scripts, build tools, and deployment scripts |
| Python | `python:3.12-slim` | Run Python scripts and deployment tools |

### Custom Image

Enter any public or private Docker image reference. This supports:

* Simple images: `alpine:latest`, `ubuntu:22.04`
* Namespaced images: `myorg/mytool:v1.0`
* Private registries: `registry.example.com:5000/team/app:1.0`

The image must be accessible from the DeployHQ build server. For private registries, ensure the build server can authenticate and pull the image.

## Step-by-Step Configuration

1. **Select Protocol:** Navigate to your server settings within DeployHQ and select **Custom Action** as your server's protocol.

2. **Choose Image Source:** Select either **Curated Template** or **Custom Image**.

   * For curated templates, select the template from the dropdown. The Docker image is set automatically.
   * For custom images, enter the full Docker image reference (e.g., `myorg/mytool:v2.0`).

3. **Enter Commands:** Add the commands to run inside the container, one per line. Commands execute sequentially in the order listed.

   ```text
   cd /data
   npm ci
   npm run deploy
   ```

4. **Configure Error Handling:** Choose whether to halt the deployment if a command fails.

   * **Halt on error (default):** The deployment stops immediately if any command returns a non-zero exit code.
   * **Continue on error:** All commands run regardless of individual failures. Failed commands are logged but do not stop the deployment.

5. **Click Create Server** to save your configuration.

## Environment Variables

Custom Actions automatically set the following environment variables in the container:

| Variable | Description |
|----------|-------------|
| `DEPLOYHQ_PROJECT` | The project name |
| `DEPLOYHQ_BRANCH` | The branch being deployed |
| `DEPLOYHQ_REVISION` | The target commit hash |
| `DEPLOYHQ_START_REVISION` | The starting commit hash |
| `DEPLOYHQ_SERVER` | The server name |
| `DEPLOYHQ_DEPLOYMENT_ID` | The unique deployment identifier |

Any [environment variables](Article: #163) you configure on the server are also exported and available to your commands.

## Configuration Files

If your project uses [configuration files](Article: #97), you can enable **Copy configuration files** on the deployment. Config files are injected into the container at their configured paths relative to `/data` before your commands run.

## Text Variables

Custom Action commands support DeployHQ [text variables](Article: #165) for dynamic values. You can use placeholders like `%branch%`, `%revision%`, and other variables in your commands.

## Example Use Cases

### Deploy to AWS S3

Using the AWS CLI template:

```text
aws s3 sync /data/ s3://my-bucket/ --delete --exclude ".git/*"
```

Set your `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` as environment variables on the server.

### Apply Kubernetes Manifests

Using the Kubernetes template:

```text
kubectl apply -f /data/k8s/
kubectl rollout status deployment/my-app -n production
```

Store your kubeconfig content in an environment variable or configuration file.

### Run Terraform

Using the Terraform template:

```text
cd /data/infrastructure
terraform init
terraform plan -out=tfplan
terraform apply tfplan
```

### Custom Build and Deploy Script

Using a custom Node.js image:

```text
cd /data
npm ci
npm run build
npm run deploy:production
```

### Run Database Migrations

Using a custom image with your runtime:

```text
cd /data
bundle install
RAILS_ENV=production bundle exec rails db:migrate
```

## Timeout Limits

Custom Action deployments have a **30-minute overall timeout** that covers the entire process, including source upload, image pull, configuration, and command execution. Each individual command also has a **30-minute timeout**. When running multiple commands, the overall timeout is the effective limit since all steps share the same 30-minute window.

## Tutorial Guides

For detailed, step-by-step tutorials on specific Custom Action use cases, see the following guides:

* [Deploy Infrastructure with Terraform](Article: deploy-with-terraform) - Manage infrastructure as code with Terraform on AWS, GCP, or Azure
* [Deploy with AWS CloudFormation](Article: deploy-with-cloudformation) - Create and update AWS CloudFormation stacks
* [Deploy to Kubernetes with kubectl](Article: deploy-to-kubernetes) - Apply manifests and manage rollouts on Kubernetes clusters
* [Deploy to Google Cloud with gcloud](Article: deploy-with-gcloud) - Deploy to App Engine, Cloud Run, GKE, and other GCP services

## Troubleshooting

**Image Pull Fails:**

* Verify the Docker image name is correct and the tag exists
* For private registries, ensure the image is accessible from the DeployHQ build server
* Check that the registry hostname and port are correct

**Command Fails:**

* Review the deployment log for the specific error output
* Verify your commands work locally in the same Docker image: `docker run --rm -v $(pwd):/data -w /data IMAGE_NAME bash -c "YOUR_COMMAND"`
* Check that required environment variables are configured on the server

**Source Code Not Found:**

* Your repository is mounted at `/data` inside the container
* Ensure your commands reference files relative to `/data`

**Permission Issues:**

* Commands must not use `sudo` - containers run with appropriate permissions
* If a command requires specific permissions, use a Docker image that has them pre-configured

**Build Server Connection Error:**

* The build server may be temporarily unavailable. Try the deployment again after a few minutes.
* If the issue persists, contact DeployHQ support.

## Cloud platform examples

Custom Actions can run any CLI tool inside a Docker container during your deployment pipeline. The following guides show how to set up Custom Action servers for popular cloud platforms:

- [Deploy to Google Cloud with gcloud](/support/deploy-with-gcloud) — push containers, update Cloud Run services, or run any `gcloud` command
- [Deploy to Kubernetes with kubectl](/support/deploy-to-kubernetes) — apply manifests and roll out deployments to any Kubernetes cluster
- [Deploy with AWS CloudFormation](/support/deploy-with-cloudformation) — provision and update CloudFormation stacks from your repository
- [Deploy Infrastructure with Terraform](/support/deploy-with-terraform) — run `terraform plan` and `terraform apply` as a deployment step
