Lal Chand
HomeAboutProjectsCase StudiesBlogWork with meContact

Lal Chand

I build AI automation and custom business systems.

Full-stack engineer and founder of Codic Systems. Islamabad, Pakistan.

info@codicsystems.comlalchand.professional@gmail.com+92 310 6846514

Quick Links

HomeAboutExperienceProjectsCase StudiesBlogWork with meContact

Social

UpworkCodic on GitHub

© 2026 Lal Chand. Client work is delivered through Codic Systems (SMC-Private) Limited.

Legal

Privacy PolicyTerms of Service

Blog

A deploy pipeline from GitHub to cPanel, and the CI check that failed for four months

By Lal Chand, founder of Codic Systems

Published 4 October 2026Last updated 10 October 20265 min read

A lot of small businesses run on cPanel hosting. It's cheap, it's familiar and a lot of PHP software expects it. It's also easy to deploy badly: dragging files over FTP and hoping.

This is the pipeline I'd build today from GitHub to cPanel. After it, the story of a CI check that reported failures for four months, which taught me more than the pipeline did.

What the pipeline should do

Keep the goal modest. A push to the main branch should produce a tested, deployed version of the site, and nothing else should touch the live files.

That gives you a few rules:

  • The repository is the source of truth. Nobody edits files on the server.
  • Tests and checks run before deployment, not after.
  • Deployment is repeatable. Running it twice gives the same result.
  • You can roll back by deploying an earlier commit.

Two ways to get files onto cPanel

Option one: cPanel's own Git deployment. cPanel can manage a Git repository and deploy from it. The repository needs a .cpanel.yml file in its top-level directory, at least one branch and a clean working tree. The file contains a deployment section with a list of tasks, usually shell commands that copy files into the live directory. The documentation warns against using wildcards that copy everything, since that can copy the .git directory into your public folder. Push deployment runs the tasks automatically when you push to the cPanel repository. Pull deployment lets you click "Update from Remote" and then "Deploy HEAD Commit".

Option two: push from GitHub Actions. A workflow runs on a push, runs your checks, then copies the built files to the host over SSH. This keeps all the logic in one place, visible in the repository, and it lets you build assets in a clean environment instead of on the host.

I lean toward the second when the project has a build step, and the first when it's a plain PHP site. Either works. What matters is picking one and not mixing them.

A sensible workflow

A GitHub Actions workflow is a YAML file in .github/workflows that runs jobs when an event happens, such as a push. Each job runs on a runner, which is the machine that executes it.

For a PHP project, the jobs I'd want are:

  1. Install dependencies from the lock file, not from a loose version range.
  2. Run the checks: linting, static analysis and tests.
  3. Build any front-end assets.
  4. Deploy, only if the earlier jobs passed and only from the main branch.

Composer's documentation is clear about why composer.lock should be committed for an application: running composer install with a lock file installs the exact versions in the file, so teammates, CI servers and production all run identical code.

Secrets

The deployment needs credentials: an SSH key or a password for the host. GitHub stores these as encrypted secrets, which you reference through the secrets context and pass in as environment variables.

A few habits from GitHub's own guidance are worth repeating. Scope secrets as narrowly as you can, for example using an environment for production only. Don't pass them on the command line where other processes might see them. And never print them, because GitHub does not redact secrets that appear in logs.

Give the deploy key only the access it needs: write access to the one site directory, not the whole account.

The check that failed for four months

Now the story.

On one project, a CI check began reporting failures. It wasn't a code problem. The host had removed Composer, and the check relied on Composer being there. Every run failed.

It stayed that way for four months.

A CI check isn't what serves pages, so nothing visibly broke. The red mark sat in the same place every day, and a mark that never changes gets ignored. A check that is always red tells you nothing. It stops being a signal and becomes decoration.

The failure wasn't the missing Composer. Hosts change things, and that's normal. The failure was that a broken check was allowed to sit there long enough for people to stop believing the checks at all. If a real problem had arrived in that window, it would have looked identical to the noise.

What I would do now

A red check gets fixed or removed. Those are the only options. A check that fails permanently and is ignored is worse than no check, because it trains people to ignore failures.

Don't depend on what the host provides. If your pipeline needs Composer, install Composer in the runner. GitHub's runners are yours to configure. The host should receive finished files, not run your build.

Pin what you can. Lock files, fixed versions of actions and a documented list of the host's capabilities. When the host changes, you want a clear place to look.

Look at the pipeline on a schedule. I'd treat a pipeline like any other piece of software: it needs someone to check it still works, not only when code changes. A weekly run on a schedule catches the "nothing has changed but it broke" cases early.

Make failure visible. A failed deploy should reach a person, by email or chat, not sit in a tab nobody opens.

The short version

  • Pick one deployment route and keep the repository as the source of truth.
  • Run checks before deploying, install dependencies from a lock file and build in the runner.
  • Store secrets as encrypted secrets, scope them narrowly and never print them.
  • Never leave a check red. Fix it, or delete it.

A pipeline that is mostly green and sometimes red is useful. One that has been red for four months is a decoration, and I'd rather have no check than that one.

Questions this article answers

Can you deploy to cPanel from GitHub?+

Yes. cPanel's Git Version Control can deploy from a repository that contains a .cpanel.yml file, and GitHub Actions can also push files to the host over SSH.

What is a .cpanel.yml file?+

A YAML file in the top level of your repository that lists the tasks cPanel runs on deployment, usually commands that copy files to the target directory.

Should composer.lock be committed?+

For an application, yes. Composer's documentation says it makes everyone, including CI servers and production, install the exact same dependency versions.

Why did a CI check fail for four months without anyone fixing it?+

The host removed Composer, the check depended on it, and a permanently red check became background noise. Nobody treated it as a signal because it never changed.

How should deployment secrets be stored in GitHub Actions?+

As encrypted secrets, scoped as narrowly as possible, passed through environment variables, and never printed in logs. GitHub does not redact secrets that you print.

Sources

  • Guide to Git deployment, cPanel documentation
  • Understanding GitHub Actions, GitHub Docs
  • Using secrets in GitHub Actions, GitHub Docs
  • Basic usage, Composer documentation

Need help with something like this?

I take on client projects through Codic Systems. See how to work with me.

Keep reading

  • Web app security basics for small businesses: a practical checklist
  • Modernising a legacy system without a big-bang rewrite
  • Working with international clients from Pakistan: time zones, contracts and getting paid