Skip to main content
Automating deployments means every push to your main branch updates your CVM without manual intervention. This guide covers four approaches: the Phala CLI in GitHub Actions, the JS SDK in GitHub Actions, a GitLab CI pipeline, and Terraform in CI.

Prerequisites

  • A Git repository with your application code and a docker-compose.yml
  • A Phala Cloud account with an API key
  • A container registry account (Docker Hub, GHCR, or similar)

Secrets Management

Regardless of which CI platform you use, store your Phala Cloud API key as a secret. Never hardcode it in workflow files. GitHub: Go to Settings > Secrets and variables > Actions and add: GitLab: Go to Settings > CI/CD > Variables and add the same values as masked variables.
Rotate your API key immediately if it’s ever exposed in logs or committed to a repository. Generate a new one from Settings > API Keys in the Phala Cloud dashboard.

GitHub Actions with the deploy action

If you only need to update a CVM from phala.toml, use the Phala deploy action. It installs the CLI, waits for the update, and returns the result as outputs. See Deploy with the GitHub Action.

GitHub Actions with CLI

This is the simplest approach. The workflow builds your Docker image, pushes it to a registry, and deploys with phala deploy.
Your docker-compose.yml should reference the image variable:
--cvm-id names the CVM to update, so each push updates my-tee-app in place and --wait waits for the update to finish. The CLI does not look up an existing CVM by --name: phala deploy -n my-tee-app always creates a new CVM, and --wait has no effect on it. Create the CVM once before the first pipeline run, for example with phala deploy -n my-tee-app -c docker-compose.yml.

GitHub Actions with JS SDK

If you need more control over the deployment (conditional logic, custom error handling, multi-step provisioning), use the SDK directly in a Node.js script.
1

Create a deploy script

Add scripts/deploy.mjs to your repository:
2

Create the workflow

GitLab CI

GitLab CI uses a similar pattern. Define a .gitlab-ci.yml in your repository root:
GitLab’s built-in container registry works well here. The $CI_REGISTRY_* variables are available automatically in every pipeline.

Terraform in CI

For infrastructure-as-code workflows, run Terraform in your pipeline. This is especially useful when you manage replicas, instance types, or environment variables declaratively.
Your infra/main.tf would look something like this:
Store your Terraform state in a remote backend (S3, GCS, or Terraform Cloud) so that CI runs can access the same state file. Without remote state, each pipeline run would try to create a new CVM instead of updating the existing one.

Verify Deployment

After your pipeline completes, confirm the CVM is running:

Troubleshooting

Authentication errors: Make sure PHALA_CLOUD_API_KEY is set correctly in your CI secrets. Test locally with phala status to verify the key works. Build failures: Ensure your Dockerfile builds locally with docker build . before pushing to CI. Deploy timeouts: If --wait times out, the update may still be running. Check the dashboard or run phala cvms get my-tee-app to see the current status. Image pull errors: Verify the image tag in your compose file matches what was pushed to the registry. For private registries, set DSTACK_DOCKER_USERNAME and DSTACK_DOCKER_PASSWORD as encrypted secrets on the CVM.