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. You don't need to write deployment scripts or SSH into the server. 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, GitLab or Bitbucket repository and turn on auto-deploy, CloudPloy adds a webhook to the repository. Every push to the app's branch triggers:
- CloudPloy receives the webhook from your Git host
- 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)
- Installs dependencies and runs the framework's build steps (composer install, npm build, etc.)
- Replaces the running container with the new one
- Checks that the new container is running and responding
If the build or the health check fails, the deployment is marked failed and its logs show what went wrong. You can redeploy after a fix or roll back to a previous deployment.
Deployment Configuration
The main settings that control deployments:
| Setting | Description | Common Values |
|---|---|---|
| Branch | Which branch triggers deploys | main, production |
| Auto-deploy | Deploy automatically on every push to the branch | On or off |
| Environment variables | Configuration applied on the next deploy or redeploy | APP_ENV, DATABASE_URL |
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: open the app and start a deploy or a redeploy. 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 REST API to trigger deployments from your own scripts or CI systems:
curl -X POST https://app.cloudploy.com/api/v1/apps/APP_ID/deploy \
-H "Authorization: Bearer $CLOUDPLOY_TOKEN" Rollbacks
You can roll back to a previous deployment from the app's deployments list in the dashboard, or with POST /api/v1/apps/APP_ID/rollback.
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, run the rollback migration on the server before rolling back the code:
# On the server, before rolling back the deployment
php artisan migrate:rollback --step=1 Deploy Environments: Staging and Production
CloudPloy has no built-in staging environments, but you can create two CloudPloy applications connected to the same Git repository on different branches:
| 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.
Database Migrations
Laravel apps can run php artisan migrate --force as part of each deploy. Keep migrations backwards-compatible so the old and new code can both run against the schema during a deploy.
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 breaking the running code with a schema change.
Build Caching for Faster Deployments
If your repository has its own Dockerfile, Docker layer caching can speed up builds. 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 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 on the app's page in the dashboard. Each deployment entry shows:
- The branch and commit it deployed
- When it ran
- Its status
- Build and deploy logs
Application logs are also available from the app's page and from GET /api/v1/apps/APP_ID/logs.
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
developbranch, which auto-deploys to a separate 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, so CloudPloy deploys only your code. Changes to posts, pages, and media on staging do not sync to production; database sync is a manual step (mysqldump/restore), and you should plan where uploads are stored (for example, a media offload plugin with object storage).
GitLab Integration
CloudPloy's webhook-based deployment works with GitHub, GitLab and Bitbucket. For GitLab, the setup is the same: connect your repository, choose the branch, turn on auto-deploy, and CloudPloy adds 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.