Automating Drupal Deployments to Shared Hosting with GitHub Actions
Deploying a Drupal site to shared hosting does not require a bespoke platform, but it does require a disciplined release sequence. I built a GitHub Actions workflow for this portfolio that validates every promoted commit, connects to the host over SSH, updates the correct environment, and verifies the resulting site.
What the pipeline needs to guarantee
A useful deployment pipeline does more than copy files. It should prove that the selected commit meets project standards before it reaches a server, deploy the same commit through every environment, and fail loudly when the resulting Drupal site is unhealthy.
- Validate Composer metadata and install locked dependencies.
- Run Drupal coding-standard and PHP syntax checks.
- Map
develop,staging, andmainto isolated environments. - Use a dedicated SSH key stored as an encrypted Actions secret.
- Import configuration, rebuild and warm caches, and run deploy hooks.
- Request important public routes after deployment.
Promote one tested commit
The key design choice is promoting an exact commit rather than rebuilding a slightly different release for each environment. Development validates the change first. Staging exercises the same revision under production-like conditions. Production receives that identical revision only after both earlier deployments pass.
This makes the branch names useful release gates: develop deploys to dev, staging deploys to stage, and main deploys to production. GitHub Actions environments also make the destination visible in each run.
Keep secrets outside the repository
The workflow uses a dedicated Hostinger SSH key and a pinned known_hosts value. Password protection credentials for dev and staging are stored as repository secrets, never committed. Production needs no Basic Authentication secret because its public routes should be directly testable.
Use Drupal-aware release steps
After the server checkout is updated, the deployment creates a database backup, enables maintenance mode, installs the reviewed Composer lock file without development dependencies, runs Drupal database updates, imports the repository-owned configuration, rebuilds caches, runs deploy hooks, warms the application, and restores the site. A trap takes the site out of maintenance mode if a later command fails.
The deployment verifies the PHP runtime before relying on Composer and Drush. On the current host, the required process functions are available, so the complete Drupal update sequence can run on the server. If a future environment removes a required capability, the pipeline should fail before it starts mutating Drupal state.
Verify the application, not just SSH
A successful SSH session only proves that a command ran. The health check confirms Drupal bootstraps, the database connects, no database updates remain, custom PHP files parse, and important routes return HTTP 200. It also checks for page-specific markers so an unrelated error page cannot pass by returning a successful status.
Make deployment state explicit
A deployment script should leave evidence about the release it actually installed. I verify the Git commit, Drupal version, database connectivity, configuration status, maintenance mode, permissions on sensitive settings files, and representative HTTP routes after the release sequence completes.
This is particularly useful on shared hosting because the infrastructure offers fewer platform-level deployment guarantees. Application-level verification becomes the proof that the environment reached the same state tested earlier in the pipeline.
Keep content flow separate from code promotion
Code and deployable configuration move from development to staging to production. Production editorial data does not move upward with those branches. When realistic data is needed elsewhere, database content moves downward through an intentional synchronization process. Keeping those directions separate avoids turning deployment into an accidental content migration.