DEV Community

Cover image for Migrating Cloudways GitHub Actions CI/CD from API Key to Access Token
Md. Asaduzzaman
Md. Asaduzzaman

Posted on

Migrating Cloudways GitHub Actions CI/CD from API Key to Access Token

Cloudways is deprecating its legacy API Key authentication and moving integrations to API Access Tokens.

If you have an existing Laravel application deployed through GitHub Actions → Cloudways, this change may require updating your CI/CD workflow before the legacy API Key reaches its end of life.

Cloudways currently lists October 15, 2026 as the end-of-life date for legacy API Keys. Existing integrations can continue using API Keys during the transition, but Cloudways recommends migrating active integrations to Access Tokens before the retirement date.

This article explains how we migrated a Laravel + GitHub Actions + Cloudways deployment without changing the rest of our deployment process.

Note: This is a community-tested implementation based on Cloudways' documented Access Token API. It is not an official Cloudways guide.


Our Original Setup

Our Laravel application was deployed using this flow:

GitHub
   │
   │ push to develop
   ▼
GitHub Actions
   │
   │ Cloudways API Key
   ▼
Cloudways
   │
   │ Git pull
   ▼
Application
   │
   │ SSH
   ▼
Laravel deployment commands
Enter fullscreen mode Exit fullscreen mode

The GitHub Actions workflow originally used:

- name: Cloudways Deployment
  uses: roelmagdaleno/cloudways-api-git-pull-action@stable
  with:
    email: ${{ secrets.CW_DEV_EMAIL }}
    api-key: ${{ secrets.CW_DEV_API_KEY }}
    server-id: ${{ secrets.CW_DEV_SERVER_ID }}
    app-id: ${{ secrets.CW_DEV_APP_ID }}
    branch-name: "develop"
    deploy-path: ''
Enter fullscreen mode Exit fullscreen mode

The important part here is:

api-key: ${{ secrets.CW_DEV_API_KEY }}
Enter fullscreen mode Exit fullscreen mode

That is the credential that needs to be migrated.


Why Do We Need to Change It?

Cloudways has introduced API Access Tokens as the replacement for the legacy API Key.

According to Cloudways, Access Tokens provide several advantages over the old API Key:

  • Separate tokens can be created for different integrations.
  • Tokens can have different permissions.
  • Tokens can have expiration periods.
  • Tokens can be revoked independently.
  • Limited Access can be used to follow the principle of least privilege.

Cloudways currently recommends Limited Access when an integration only needs selected API operations.


The Problem With Our Existing GitHub Action

Our existing deployment action expects the old authentication model:

email:
api-key:
server-id:
app-id:
Enter fullscreen mode Exit fullscreen mode

Cloudways' newer API authentication uses an Access Token directly as a Bearer token.

The relevant request looks like:

POST /api/v1/git/pull
Authorization: Bearer YOUR_ACCESS_TOKEN
Enter fullscreen mode Exit fullscreen mode

Cloudways documents the Git deployment API using the /git/pull endpoint and Access Token authentication.

Rather than waiting for our existing third-party GitHub Action to change its authentication interface, we decided to call the Cloudways API directly from GitHub Actions using curl.

This keeps the deployment architecture almost exactly the same.


The New Deployment Flow

After the migration, our flow became:

GitHub
   │
   │ push to develop
   ▼
GitHub Actions
   │
   │ Cloudways Access Token
   ▼
Cloudways API
   │
   │ POST /git/pull
   ▼
Cloudways Application
   │
   │ SSH
   ▼
Laravel deployment commands
Enter fullscreen mode Exit fullscreen mode

The important difference is simply:

OLD:

Email + API Key
       ↓
Cloudways API


NEW:

Access Token
       ↓
Cloudways API
Enter fullscreen mode Exit fullscreen mode

The SSH part of the deployment remains independent.


Step 1 — Create a Cloudways Access Token

Go to your Cloudways Platform and open:

Profile → API Integration

Cloudways provides an Access Token section where you can create and manage tokens.

Create a token specifically for your GitHub Actions deployment.

For example:

Name:
Promise Assets - GitHub Actions Staging
Enter fullscreen mode Exit fullscreen mode

Using a descriptive name is useful because Cloudways allows multiple Access Tokens for different integrations.


Step 2 — Choose the Appropriate Permission

For a deployment integration, you generally don't need unrestricted access to your entire Cloudways account.

Cloudways provides:

  • Limited Access
  • Read-Only Access
  • Full Access

For an integration that performs a Git deployment, Limited Access is the appropriate approach when the required Git endpoint is available in the permission selector.

Look for the Git-related deployment operation corresponding to:

POST /git/pull
Enter fullscreen mode Exit fullscreen mode

Cloudways recommends Limited Access for integrations that only require specific API operations.

Avoid giving Full Access unless your integration actually requires it.


Step 3 — Store the Token in GitHub Secrets

Do not put the Access Token directly into your YAML file.

Instead, create a GitHub repository secret:

CW_DEV_ACCESS_TOKEN
Enter fullscreen mode Exit fullscreen mode

Your deployment-related secrets can then look like:

CW_DEV_ACCESS_TOKEN
CW_DEV_SERVER_ID
CW_DEV_APP_ID
CW_DEV_HOST
CW_DEV_SSH_USER
CW_DEV_SSH_PASS
Enter fullscreen mode Exit fullscreen mode

Cloudways explicitly recommends treating Access Tokens as sensitive credentials and not publishing them in source code, screenshots, public repositories, or other publicly accessible locations.


Step 4 — Replace the Old Cloudways Deployment Action

Previously, we had:

- name: Cloudways Deployment
  uses: roelmagdaleno/cloudways-api-git-pull-action@stable
  with:
    email: ${{ secrets.CW_DEV_EMAIL }}
    api-key: ${{ secrets.CW_DEV_API_KEY }}
    server-id: ${{ secrets.CW_DEV_SERVER_ID }}
    app-id: ${{ secrets.CW_DEV_APP_ID }}
    branch-name: "develop"
    deploy-path: ''
Enter fullscreen mode Exit fullscreen mode

We replaced that with a direct API request:

- name: Cloudways Deployment
  run: |
    curl --fail-with-body --request POST \
      --url https://api.cloudways.com/api/v1/git/pull \
      --header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}" \
      --header "Content-Type: application/x-www-form-urlencoded" \
      --data-urlencode "server_id=${{ secrets.CW_DEV_SERVER_ID }}" \
      --data-urlencode "app_id=${{ secrets.CW_DEV_APP_ID }}" \
      --data-urlencode "branch_name=develop" \
      --data-urlencode "deploy_path="
Enter fullscreen mode Exit fullscreen mode

This is the key change in the migration.


Why Are We Using curl?

GitHub-hosted Ubuntu runners already have curl available.

Instead of relying on a third-party GitHub Action to handle Cloudways authentication, we're making the API request ourselves.

This gives us direct control over:

Authorization
Server ID
Application ID
Branch
Deploy path
Enter fullscreen mode Exit fullscreen mode

It also means that the CI/CD workflow doesn't depend on whether the third-party action has already implemented Cloudways' new Access Token authentication.


What Does Each Parameter Do?

Access Token

--header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}"
Enter fullscreen mode Exit fullscreen mode

This replaces the old:

api-key: ${{ secrets.CW_DEV_API_KEY }}
Enter fullscreen mode Exit fullscreen mode

Cloudways' newer authentication model uses the Access Token as a Bearer token.

Server ID

--data-urlencode "server_id=${{ secrets.CW_DEV_SERVER_ID }}"
Enter fullscreen mode Exit fullscreen mode

This identifies the Cloudways server.

Application ID

--data-urlencode "app_id=${{ secrets.CW_DEV_APP_ID }}"
Enter fullscreen mode Exit fullscreen mode

This identifies the application that should receive the deployment.

Branch

--data-urlencode "branch_name=develop"
Enter fullscreen mode Exit fullscreen mode

Our staging environment deploys the develop branch.

Change this if your workflow uses another branch.

Deploy Path

--data-urlencode "deploy_path="
Enter fullscreen mode Exit fullscreen mode

We leave the deploy path empty because the Cloudways application is already configured with its deployment directory.


Complete GitHub Actions Workflow

Here is the complete workflow used for our Laravel staging deployment:

name: Staging Deployment

on:
  push:
    branches: ["develop"]
  pull_request:
    branches: ["develop"]
    types:
      - closed

jobs:
  deploy:
    runs-on: ubuntu-latest

    env:
      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Cloudways Deployment
        run: |
          curl --fail-with-body --request POST \
            --url https://api.cloudways.com/api/v1/git/pull \
            --header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}" \
            --header "Content-Type: application/x-www-form-urlencoded" \
            --data-urlencode "server_id=${{ secrets.CW_DEV_SERVER_ID }}" \
            --data-urlencode "app_id=${{ secrets.CW_DEV_APP_ID }}" \
            --data-urlencode "branch_name=develop" \
            --data-urlencode "deploy_path="

      - name: Executing Linux Commands
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.CW_DEV_HOST }}
          username: ${{ secrets.CW_DEV_SSH_USER }}
          port: 22
          password: ${{ secrets.CW_DEV_SSH_PASS }}
          script: |
            # Navigate to Promise Assets application directory
            for d in $(find $HOME /home -type d -name "public_html" 2>/dev/null); do
                if [ -d "$d/Modules/CRM" ] || [ -f "$d/app/Console/Commands/SyncPermissionsCommand.php" ]; then
                    cd "$d"
                    break
                fi
            done

            echo "Deploying in: $(pwd)"

            if [ ! -f "artisan" ]; then
                echo "ERROR: Could not locate promise-assets application directory!"
                exit 1
            fi

            echo "executing composer install...."
            composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

            echo "clearing old cache and discovering packages...."
            php ./artisan optimize:clear
            php ./artisan package:discover --ansi

            echo "running migration...."
            php artisan migrate --force

            # Sync permissions
            echo "syncing permissions..."
            php artisan permission:sync 2>/dev/null || true

            echo "clearing cache...."
            php ./artisan optimize:clear

            echo "done...."
Enter fullscreen mode Exit fullscreen mode

What Changed From the Old Workflow?

The rest of the workflow stays almost exactly the same.

Before

uses: roelmagdaleno/cloudways-api-git-pull-action@stable

with:
  email: ${{ secrets.CW_DEV_EMAIL }}
  api-key: ${{ secrets.CW_DEV_API_KEY }}
Enter fullscreen mode Exit fullscreen mode

After

run: |
  curl --request POST \
    --url https://api.cloudways.com/api/v1/git/pull \
    --header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}"
Enter fullscreen mode Exit fullscreen mode

The SSH deployment remains unchanged.

This is useful because your Laravel deployment commands don't need to know anything about the Cloudways API authentication change.


Why Didn't We Add git_url?

Cloudways' current Git deployment documentation includes git_url in its API example.

However, in our existing setup the Cloudways application was already configured with its Git repository, and our previous deployment workflow successfully triggered a Git pull without explicitly providing the repository URL.

Therefore, our migration initially kept the existing application configuration and changed only the authentication mechanism.

The practical lesson is:

Don't unnecessarily change a working deployment configuration while migrating authentication.

If your Cloudways API response indicates that git_url is required for your particular application/API configuration, add it to the request.

For example:

--data-urlencode "git_url=${{ secrets.CW_DEV_GIT_URL }}"
Enter fullscreen mode Exit fullscreen mode

The repository URL should then be stored as a GitHub Secret rather than hard-coded if you prefer to keep your deployment configuration centralized.


Testing the Migration

Before deleting the old API Key, test the new workflow.

A safe migration sequence is:

1. Create Access Token
        ↓
2. Configure GitHub Secret
        ↓
3. Update GitHub Actions
        ↓
4. Push to develop
        ↓
5. Verify Cloudways Git deployment
        ↓
6. Verify SSH deployment
        ↓
7. Test another deployment
        ↓
8. Remove old API Key
Enter fullscreen mode Exit fullscreen mode

Don't immediately delete the old credential.

Keeping it temporarily gives you a fallback while validating the new authentication.


Useful Error Checks

401 Unauthorized

If you receive something like:

401 Unauthorized
Enter fullscreen mode Exit fullscreen mode

check:

  • Access Token value
  • GitHub Secret name
  • Token expiration
  • Authorization: Bearer syntax

Make sure the secret is referenced exactly:

${{ secrets.CW_DEV_ACCESS_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

403 Forbidden

A 403 generally indicates an authorization/permission problem.

Check the Access Token's Limited Access configuration and make sure it has permission for the Git deployment operation required by your workflow.

Cloudways Access Tokens can be configured with specific API endpoint permissions.


400 Bad Request

A 400 usually means that one or more parameters aren't acceptable.

Check:

server_id
app_id
branch_name
deploy_path
Enter fullscreen mode Exit fullscreen mode

If your Cloudways configuration/API response requires the repository URL, also provide:

git_url
Enter fullscreen mode Exit fullscreen mode

Cloudways provides an API Playground that can be useful for testing API requests independently from GitHub Actions.


Testing With the Cloudways API Playground

Cloudways provides an API Playground where you can authorize with an Access Token and test API endpoints.

This can be useful before troubleshooting GitHub Actions.

The basic process is:

  1. Open the Cloudways API Playground.
  2. Click Authorize.
  3. Select the Access Token authentication option.
  4. Enter your Access Token.
  5. Select the relevant Git endpoint.
  6. Provide the required parameters.
  7. Execute the request.

Cloudways notes that actions performed through the API Playground operate against the authenticated account, so use care when testing endpoints that modify infrastructure or applications.


Don't Confuse API Authentication With SSH Authentication

One thing that initially made this migration confusing for us was that the deployment has two separate authentication mechanisms.

Cloudways API

GitHub Actions
       │
       │ Access Token
       ▼
Cloudways API
       │
       ▼
Git Pull
Enter fullscreen mode Exit fullscreen mode

SSH

GitHub Actions
       │
       │ SSH credentials
       ▼
Cloudways Server
       │
       ▼
Laravel commands
Enter fullscreen mode Exit fullscreen mode

The Cloudways Access Token replaces the API Key.

It does not replace your SSH credentials.

Therefore, this can remain:

uses: appleboy/ssh-action@v1.0.3
Enter fullscreen mode Exit fullscreen mode

with your existing SSH configuration.


Our Laravel Deployment Commands

Once Cloudways has pulled the latest code, our workflow connects over SSH and performs the Laravel deployment tasks.

The commands include:

composer install
Enter fullscreen mode Exit fullscreen mode

followed by:

php artisan optimize:clear
php artisan package:discover --ansi
Enter fullscreen mode Exit fullscreen mode

Then migrations:

php artisan migrate --force
Enter fullscreen mode Exit fullscreen mode

Then our application-specific permission synchronization:

php artisan permission:sync
Enter fullscreen mode Exit fullscreen mode

And finally:

php artisan optimize:clear
Enter fullscreen mode Exit fullscreen mode

This part of the deployment does not depend on the Cloudways API Key.


GitHub Secrets After Migration

Our new secrets look like:

CW_DEV_ACCESS_TOKEN
CW_DEV_SERVER_ID
CW_DEV_APP_ID
CW_DEV_HOST
CW_DEV_SSH_USER
CW_DEV_SSH_PASS
Enter fullscreen mode Exit fullscreen mode

The old credentials:

CW_DEV_EMAIL
CW_DEV_API_KEY
Enter fullscreen mode Exit fullscreen mode

are no longer required by the new Cloudways API call.

We recommend keeping the old API Key temporarily during migration and removing it only after the new deployment has been successfully tested.


Security Considerations

An Access Token is effectively a credential for your Cloudways account/API.

Never commit this:

CW_DEV_ACCESS_TOKEN: "actual-token-here"
Enter fullscreen mode Exit fullscreen mode

to your repository.

Instead use:

${{ secrets.CW_DEV_ACCESS_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Cloudways explicitly advises users not to expose Access Tokens in public repositories, screenshots, support tickets, chats, or other publicly accessible locations.

Also consider creating a dedicated token for each deployment environment.

For example:

Production:
Cloudways - GitHub Actions - Production

Staging:
Cloudways - GitHub Actions - Staging
Enter fullscreen mode Exit fullscreen mode

This makes revocation and troubleshooting much easier.

Cloudways supports multiple Access Tokens, with separate permissions and expiration settings.


One More Improvement: Don't Use Full Access Unless You Need It

When creating the token, you may see options such as:

Limited Access
Read-Only Access
Full Access
Enter fullscreen mode Exit fullscreen mode

For a deployment integration, don't automatically choose Full Access.

If the Git deployment endpoint is available under Limited Access, select only the required Git operation.

This follows the principle of least privilege and limits what a compromised CI/CD credential can do. Cloudways itself recommends Limited Access for integrations that only require selected API operations.


What About the Old API Key?

Cloudways currently states that the legacy API Key is scheduled for end of life on:

October 15, 2026

Existing users can continue using the old API Key during the transition period, but Cloudways recommends migrating active integrations before the retirement date. After the deprecation period, legacy API Keys will be revoked and integrations that still depend on them may stop working.

So this isn't just a cosmetic change.

If your CI/CD pipeline still contains:

api-key: ${{ secrets.CW_DEV_API_KEY }}
Enter fullscreen mode Exit fullscreen mode

it's worth identifying and migrating it before the deadline.


Final Architecture

After the migration, our deployment architecture looks like this:

                     GitHub
                        │
                        │ push
                        ▼
                GitHub Actions
                        │
                        │ Access Token
                        ▼
              Cloudways API
                        │
                        │ POST /git/pull
                        ▼
              Cloudways Application
                        │
                        │ latest code
                        ▼
                 SSH Connection
                        │
          ┌─────────────┼──────────────┐
          │             │              │
          ▼             ▼              ▼
      Composer       Migrations    Permissions
          │             │              │
          └─────────────┼──────────────┘
                        │
                        ▼
                  Cache Clear
                        │
                        ▼
                    Deployed
Enter fullscreen mode Exit fullscreen mode

The main takeaway is that you don't necessarily need to redesign your entire CI/CD pipeline just because Cloudways is retiring the legacy API Key.

If your existing Cloudways application is already configured for Git deployment, you can keep the surrounding deployment process and replace the legacy API authentication with an Access Token-based /git/pull request.


Conclusion

Cloudways' move from API Keys to Access Tokens requires existing CI/CD integrations to be reviewed before October 15, 2026.

For a Laravel application using GitHub Actions, one practical migration path is:

Legacy API Key
      ↓
Cloudways /git/pull
      ↓
Replace with
      ↓
Access Token + Bearer authentication
      ↓
Cloudways /git/pull
      ↓
Continue using SSH for Laravel deployment
Enter fullscreen mode Exit fullscreen mode

In our case, this allowed us to migrate the Cloudways authentication layer without changing the Composer, migration, permission-sync, or cache-clearing stages of the existing Laravel deployment.

If you're currently using a third-party GitHub Action that only supports Cloudways' legacy API Key, a direct API call from GitHub Actions is one option worth considering while migrating to the new authentication system.

Official Cloudways documentation:

Top comments (0)