In a recent post I talked about a Ddev-GitLab-cPanel workflow that removed most of the pain points and irritants related to the deployment of routine updates or new features to cPanel hosted websites.
In this post, I drill down into key details of the cPanel's repository architecture and deploy script (.cpanel.yml) for which I feel there isn't quite enough information to easily achieve an ideal and secure setup.
When creating a cPanel repo from the UI (cPanel Β» Tools Β» Files Β» Git Version Control), by default the Repository Path is /home/your-cpanel-username/repositories/repo-name. Of course, it's easy to change this to e.g. public_html/drupal1, which is what I did after reading through the docs and not finding any explicit warnings against that. I got it wrong! It took a while before I could fully realized that using a project root as a repository path was not best practice.
This 'wrong' had advantages: no code duplication, I could commit changes from Prod back to the remote repo and getting the deploy script to work was easy, so it seemed. But there was a somewhat invisible downside to this: it meant the .git and .ddev landed in the project root and that's not secure.
As it turns out, the suggested Repository Path i.e. /repositories/repo-name is both best practice and secure because it lies outside of public_html. Here's a before and after comparison:
Directory Structure Comparison
Development vs Production File Organization
β οΈ Issues:
- Development files (.git, .ddev) exposed in web directory
- Security risk - version control accessible via web
- Mixed development and production environments
β Benefits:
- Clean separation of development and production
- No development files exposed to web
- Enhanced security and organization
- Full dependencies in production, minimal in development
Going for Best Practices means accepting some code duplication, though with a proper .gitignore strategy this quickly becomes a non-issue, really.
The biggest impact of this architecture change is that the deploy script (.cpanel.yml) becomes a bit more complex because it now has to take into account the fact that the cPanel's repository and the site's root are separate locations. Here's a partial example that illustrates this:
--- deployment: tasks: # Set variables - export REPO_PATH=/home/bisonble/repositories/drupal_site - export DEPLOYPATH=/home/your-cpanel-username/public_html/drupal_site - export LOG_PATH=/home/your-cpanel-username/public_html # Initialize custom deploy.log - echo "=== Deployment Started ===" > $LOG_PATH/deploy.log - echo "Deployment started at $(date)" >> $LOG_PATH/deploy.log - echo "Current directory $(pwd)" >> $LOG_PATH/deploy.log - echo "" >> $LOG_PATH/deploy.log # Copy files and directories - echo "=== Copying visible directories and files ===" >> $LOG_PATH/deploy.log - /bin/cp -R config recipes web composer.json composer.lock $DEPLOYPATH/ - echo "" >> $LOG_PATH/deploy.log # Run commands - echo "=== Running Composer Install ===" >> $LOG_PATH/deploy.log - cd $DEPLOYPATH && /usr/local/bin/composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev >> $LOG_PATH/deploy.log 2>&1 || echo "Composer install failed" >> $LOG_PATH/deploy.log - echo "" >> $LOG_PATH/deploy.log - echo "=== Restoring Custom .htaccess ===" >> $LOG_PATH/deploy.log - echo "Current directory $(pwd)" >> $LOG_PATH/deploy.log - cd $REPO_PATH && /bin/cp web/.htaccess $DEPLOYPATH/web/.htaccess >> $LOG_PATH/deploy.log 2>&1 || echo "Failed to restore .htaccess" >> $LOG_PATH/deploy.log - echo "" >> $LOG_PATH/deploy.log - echo "=== Running Drush Deploy ===" >> $LOG_PATH/deploy.log - cd $DEPLOYPATH && vendor/bin/drush deploy >> $LOG_PATH/deploy.log 2>&1 || echo "Drush deploy failed with exit code $?" >> $LOG_PATH/deploy.log - echo "" >> $LOG_PATH/deploy.log # It's a wrap - echo "=== Deployment Complete ===" >> $LOG_PATH/deploy.log - echo "Deployment finished at $(date)" >> $LOG_PATH/deploy.log - echo "" >> $LOG_PATH/deploy.log
Final Notes
Deploying in the cPanel UI occurs in 2 separate steps, each step has its dedicated button:
- Update from Remote: clicking this button adds a pull job to a queue; I usually wait 10 minutes just to make sure the system can complete this process;
- Deploy HEAD Commit: clicking this button adds a deploy job to a queue; stay on the page until you get the green successful system message; this can easily take 90 seconds if e.g. you are deploying a Drupal core minor version update, don't forget that a deployment requires composer install and drush deploy to run in succession;
About debugging tools:
- By default, the cPanel provides a very useful log @ e.g. /home/your-cpanel-username/.cpanel/logs/vc_1721761233.5389_git_deploy.log, make sure to check it out;
- Also, the .cpanel.yml example provided above shows how you can create your own log to help you debug and fine-tune your script;
Common errors
- Error: (XID blabla) β/usr/local/cpanel/3rdparty/bin/gitβ reported error code β128β when it ended: git@remote-repo.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
- Solution: The error suggests the SSH key authentication isn't properly set up in the cPanel.
- On cPanel server, check if private key exists: ls -l ~/.ssh/your-cPanel-key*
- Verify SSH connection: ssh -Tv git@remote-repo.com -i ~/.ssh/your-cPanel-key
- Ensure key permissions are correct: chmod 600 ~/.ssh/your-cPanel-key && chmod 700 ~/.ssh
- Add to SSH agent: eval $(ssh-agent -s) && ssh-add ~/.ssh/your-cPanel-key
- Create/edit ~/.ssh/config:
Host remote-repo.com
IdentityFile ~/.ssh/your-cPanel-key
User git