CI/CD Hosting on CloudPloy
CloudPloy is built around continuous deployment: every push to your connected Git branch triggers an automatic deployment to your server. No deployment scripts, no SSH commands, no manual steps. This page explains how CloudPloy handles the CD (continuous deployment) side of your pipeline, how to add CI (continuous integration) with GitHub Actions, and how to structure the full workflow for PHP, Laravel, Symfony, and WordPress applications.
How CloudPloy Handles Continuous Deployment
When you connect a GitHub repository to a CloudPloy application, CloudPloy installs a webhook on your repository. Every push to the configured branch triggers:
- CloudPloy receives the webhook from GitHub
- Clones the repository at the new commit SHA
- Builds a Docker image from your Dockerfile (or auto-detects the framework and uses a default image)
- Runs pre-deploy commands (composer install, npm build, etc.)
- Starts the new container and verifies the health check passes
- Switches traffic from the old container to the new one
- Stops the old container after a 30-second drain period
The switch from step 6 is atomic - users see either the old version or the new version, never a mix. This is zero-downtime deployment. If the health check fails in step 5, the deployment is aborted and the old container continues serving traffic.
Deployment Configuration
Configure deployment behavior in App > Settings > Deployments:
| Setting | Description | Common Values |
|---|---|---|
| Branch | Which branch triggers deploys | main, production |
| Health check path | URL CloudPloy polls to verify the new container is ready | /health, / |
| Pre-deploy commands | Commands run inside the container before traffic switches | php artisan migrate --force |
| Post-deploy commands | Commands run after traffic switches to the new container | php artisan cache:clear |
| Rollback on failure | Automatically revert if health check fails | Enabled by default |
Adding CI with GitHub Actions
CloudPloy handles deployment, but it does not run your test suite. For full CI/CD, use GitHub Actions to run tests before CloudPloy deploys. The typical setup:
- GitHub Actions runs on every pull request and every push to main
- If tests pass, GitHub merges (or you merge) to the deployment branch
- The push to the deployment branch triggers CloudPloy's deployment webhook
A complete CI workflow for a Laravel application:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql, redis
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Copy .env
run: cp .env.example .env
- name: Generate app key
run: php artisan key:generate
- name: Run migrations
run: php artisan migrate --force
env:
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: testing
DB_USERNAME: root
DB_PASSWORD: password
- name: Run tests
run: php artisan test --parallel
env:
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: testing
DB_USERNAME: root
DB_PASSWORD: password When tests pass on main, the push triggers CloudPloy's webhook and the deployment proceeds. If tests fail, the push to main would be blocked by branch protection rules.
Manually Triggering Deployments
You can trigger a deployment without pushing code from the CloudPloy dashboard - go to App > Deployments and click Deploy. This is useful for:
- Applying environment variable changes without a code change
- Re-deploying after a failed deployment
- Testing a rollback and then re-deploying the latest version
You can also use CloudPloy's API to trigger deployments programmatically from your own scripts or CI systems.
Rollbacks
Every deployment creates a snapshot. You can roll back to any previous deployment from App > Deployments - select a previous deployment and click Rollback. The rollback goes through the same health-check process as a normal deployment: the old image is restarted, verified, and traffic switches back to it.
For Laravel apps, note that a code rollback does not automatically reverse database migrations. If your latest migration changed the schema in a way that the rolled-back code cannot handle, you need to run the rollback migration manually before rolling back the code:
# In App > Terminal, before rolling back the deployment
php artisan migrate:rollback --step=1 Deploy Environments: Staging and Production
The standard CI/CD workflow for CloudPloy applications uses two separate CloudPloy applications connected to the same Git repository:
| App | Branch | Purpose |
|---|---|---|
| staging-myapp | develop | Automatically deploys every push for QA verification |
| production-myapp | main | Deploys on every merge to main (after PR approval) |
Each CloudPloy application has its own environment variables, so staging connects to a separate database and uses test API keys. The code is identical - only the configuration differs.
This gives you a complete promotion flow: feature branch - pull request - CI runs tests - merge to develop - auto-deploys to staging - QA verifies - merge to main - auto-deploys to production.
Pre-Deploy Commands for Database Migrations
The safest pattern for zero-downtime migrations is to run them as a pre-deploy command - after the new container starts but before traffic switches:
# In App > Settings > Deployments > Pre-deploy commands
php artisan migrate --force This works for migrations that are backwards-compatible: adding nullable columns, adding indexes, creating new tables. For migrations that break the old schema (dropping columns, changing column types), use the expand-contract pattern:
- Expand: Deploy code that handles both old and new schema. Run migration to add new column (nullable).
- Migrate: Backfill data to new column.
- Contract: Deploy code that only uses new column. Run migration to drop old column or add constraints.
This requires two separate deployments but avoids any downtime from schema changes.
Build Caching for Faster Deployments
Docker layer caching speeds up builds significantly. Structure your Dockerfile to copy dependency files before application code - this way the dependency installation layer is cached and only re-runs when composer.json or package.json changes:
# Good - dependencies cached separately from code
FROM cloudploy/php:8.3-fpm
WORKDIR /var/www/html
# Copy only dependency files first (cached unless these change)
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
# Copy application code (cache invalidated on every push)
COPY . .
RUN composer run-script post-autoload-dump With this structure, a code-only change skips the composer install step and builds significantly faster.
Monitoring Deployments
View deployment history, logs, and status in App > Deployments. Each deployment entry shows:
- The commit SHA that triggered it
- Start time and duration
- Status (deploying, healthy, failed, rolled back)
- Build logs for the Docker image build
- Health check results
Build and deploy logs are also accessible from App > Logs. Set up monitoring and alerting to get notified when a deployment fails or your application becomes unhealthy after a deploy.
WordPress CI/CD
WordPress sites benefit from CI/CD for plugin and theme development. The typical workflow:
- Develop themes and custom plugins locally with Docker
- Push to a feature branch, which auto-deploys to a staging CloudPloy app
- Verify changes on staging with a real database (cloned from production)
- Merge to main, which deploys to production
For WordPress, the database and wp-content/uploads directory are not in the Git repository. They live on the persistent volume attached to your server. Changes to posts, pages, and media on staging do not automatically sync to production - database sync is a manual step (mysqldump/restore).
GitLab Integration
CloudPloy's webhook-based deployment works with any Git host, not just GitHub. For GitLab, the setup is the same: connect your repository, configure the branch, and CloudPloy installs the webhook. GitLab CI/CD pipelines run your test suite; CloudPloy handles deployment when code lands on your deployment branch.
See the GitLab integration guide for step-by-step setup instructions.