Knowledge Base | BlackHOST

Automatic deployments to cPanel with GitHub Actions

In the era of agentic development, version tracking is a must. Agents produce non-deterministic output, and the only sane way to work with that is a history you can inspect and revert. With that comes the need for automated deployments and rollbacks, and lucky for us this existed long before the agents came. Its name is CI/CD.

What is CI/CD and why AI agents need it?

Continuous Integration and Continuous Deployment (or Delivery) is a software development method that automates the whole process of building, testing and deploying in a fast, reliable and repeatable way. In simpler words: the developer writes the code, commits it to Git, pushes it somewhere, and that push triggers the pipeline. The code gets tested and, if all lights are green, deployed.

In the agentic era, where the developer supervises agents instead of typing every line, CI/CD has never been more needed:

  1. Agents mess up sometimes (still). Version control and a one-command rollback are a must.
  2. The agent never needs the keys. It writes the code and the pipeline; GitHub holds the credentials; the server only ever talks to the pipeline. You review every change before it ships, and the agent cannot ship anything on its own.
  3. Tokens cost money. Why spend them on repetitive upload work when all the agent has to do is commit and push.

Setting up basic CI/CD with cPanel and GitHub over SFTP

Enter Deployer 9000 (DEP 9000), our open-source GitHub Action for deployments over FTP, SFTP and rsync. All you need is one workflow file in your repository and a few secrets. So let’s get started.

This guide assumes you know your way around Git and pushing commits to GitHub. The repository can be private, that changes nothing below. SFTP needs SSH access on your cPanel account.

1. The workflow file

In your repository, create .github/workflows/deploy.yml with the following content:

name: Deploy

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Code Deployment
        uses: Black-HOST/deployer@v1
        with:
          protocol: sftp
          server: ${{ secrets.SFTP_HOST }}
          port: ${{ secrets.SFTP_PORT }}
          username: ${{ secrets.SFTP_USER }}
          ssh_key: ${{ secrets.SFTP_KEY }}
          # the key password, if the key has one; an empty secret is ignored
          password: ${{ secrets.SFTP_KEY_PASS }}
          # SFTP paths are real paths, relative to the login's home directory
          remote_dir: "public_html"
          # a custom list replaces the default one, so the dotfile rules stay in front
          exclude: ".*,.*/,*.md"

Every push to main now runs this job: check out the repository, hand it to DEP 9000, upload it to public_html over SFTP. You might have noticed the secrets. references. Those are the credentials, and they never live in the file. We create them in step 3; first we need a key.

2. The deploy key

Instead of a password, the pipeline logs in with an SSH key that exists only for this purpose. If someone leaves the project, you delete the key in cPanel and the pipeline is locked out, nothing to rotate.

In cPanel, open SSH Access > Manage SSH Keys > Generate a New Key. Give it a name such as deploy_key, choose RSA with 4096 bits and click Generate Key as shown bellow.

cPanel generates RSA keys only. The modern standard for SSH keys is ed25519: shorter, faster, nothing to size. If you prefer it, you cab create the key on your computer with the following command, assuming you have `OpenSSH` installed:

ssh-keygen -t ed25519 -f deploy_key -N "" -C "github deploy"

This gives you deploy_key (private, goes to GitHub) and deploy_key.pub (public, goes to the server). Do not add either to the repository. In cPanel, open Manage SSH Keys > Import Key, paste the content of deploy_key.pub as the public key and click Import.

Either way, the last step is the same: under Manage SSH Keys > Public Keys, click Manage next to the key, then Authorize. Until you do, the key exists but cannot log in.

3. The secrets

Head to Settings > Secrets and variables > Actions in your GitHub repository and create these:

SecretValue
SFTP_HOSTyour domain, or the server hostname shown in cPanel
SFTP_PORTthe SSH port, 22 unless your host says otherwise
SFTP_USERyour cPanel username
SFTP_KEYthe private key as text, not a file name: paste the whole content, BEGIN and END lines included, from Manage SSH Keys > Private Keys > View/Download in cPanel, or from the deploy_key file if you created it yourself
SFTP_KEY_PASSthe key password, only if you set one for your SSH key

Only the key and its password are real secrets. We keep the rest as secrets too, so there is one place to look. If you prefer, host and port can be repository variables instead (server: ${{ vars.SFTP_HOST }}), which are visible in the settings and in the logs. That helps when a connection fails, because secret values are masked as *** in the log. At the end you should get something like this in your GitHub settings:

4. Push

Commit the workflow file and push. The Actions tab in your repository shows the run; when it turns green, the site is live. The first deploy uploads everything; from then on, every push to main is a deployment.

What the workflow actually does

A few lines in that file deserve a second look, because they are where cPanel deployments usually go wrong.

Rolling back

This is the part the intro promised. The agent shipped something ugly, the site is broken, and it is 23:40. In the repository:

git revert HEAD
git push

The revert is a new commit that undoes the last one, the push triggers the pipeline, and a minute later the previous version is live. No FTP client, no “which files did it touch”, no restoring from a backup. If the bad commit added files that should disappear too, that is what delete: "true" is for. A rollback is just another commit, which also means it is in the history, reviewable, and revertable itself.

The alternative will be to re-trigger deploy action on specific commit that we know that we know everything was ok.

Let your agent do it

You are building with Claude Code, Cursor or a similar tool? Then the setup above is the agent’s job, not yours. We’ve created the CI/CD playground, simple repo where all the examples live. Each branch is an example on its own for different protocols and various setups. Once you pick your preferred branch/method you can head down to the section Let your agent do it copy the prompt for your agent and the agent takes over. The private key goes into a GitHub secret, never into the chat or the repository. From that moment, “deploy” means “commit and push”, and that is something an agent can be trusted with.

Where to go from here

This article pretty much covers the basics of DEP 9000 capabilities, so if your setup needs something more advanced you can find all the test cases we’ve been testing DEP 9000 against in in its automated workflows: https://github.com/Black-HOST/deployer/tree/master/.github/workflows. Yep we do tests on all changes and we plan to keep this product as boring as possible by staying backward compatible within all of its versions.

In case that your cPanel account dosent have SSH access, then in our CI/CD playground you can find an FTP example that does the same over FTPS with a password.

Or if you are deploying from GitLab, then we have DEP 9000 on Docker Hub as blackhost/deployer for now the DEP 9000 README has the instructions on how can you setup this with .gitlab-ci.yml.

Exit mobile version