CloudPloy

Docker Container Hosting on CloudPloy

Every application on CloudPloy runs inside a Docker container. This is not optional infrastructure overhead - it is the foundation that makes CloudPloy's managed hosting work: consistent environments across staging and production, instant rollbacks, isolated dependencies, and zero "works on my machine" problems. This page explains how Docker is used under the hood and what you can customize for your PHP, Laravel, or WordPress application.

Why CloudPloy Uses Docker

Traditional shared hosting runs all customer applications on the same server with shared PHP, MySQL, and file system access. One poorly-configured application can crash others. Dependency conflicts between customers are common.

With Docker, each CloudPloy application gets:

What is Isolated Why it Matters
PHP version and extensions Run PHP 8.1 for one app and PHP 8.3 for another on the same server
File system Applications cannot access each other's files
Process space A crashed app does not affect other containers
Network namespace Apps communicate only through explicitly configured ports
Resource limits CPU and memory limits prevent one app from starving others
Environment variables Credentials are injected at runtime, not shared via config files

CloudPloy's Base Images

CloudPloy maintains optimized Docker base images for each supported runtime. These images are built on Alpine Linux for minimal size, security-patched regularly, and pre-configured for production use:

Base Image What is Included Best For
cloudploy/php:8.3-fpm PHP-FPM 8.3, common extensions (pdo, mbstring, gd, curl, zip) Laravel, Symfony, custom PHP apps
cloudploy/php:8.2-fpm PHP-FPM 8.2, same extension set Laravel LTS, older Symfony apps
cloudploy/php:8.1-fpm PHP-FPM 8.1, maintained for security updates Apps not yet migrated to PHP 8.2+
cloudploy/wordpress:6 PHP-FPM 8.2, WP-CLI, WordPress-specific extensions WordPress and WooCommerce sites
cloudploy/node:20 Node.js 20 LTS, npm, yarn Node.js applications, SSR frontends

If you do not include a Dockerfile in your repository, CloudPloy automatically selects the appropriate base image based on your detected framework.

Part 1: Using Your Own Dockerfile

Add a Dockerfile to your repository root to customize your container. CloudPloy detects it automatically and builds from it on every deployment.

Minimal Laravel Dockerfile

FROM cloudploy/php:8.3-fpm

# Install application dependencies
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts

# Copy application code
COPY . .

# Run post-install scripts (key generation, etc.)
RUN composer run-script post-autoload-dump

# Set correct permissions
RUN chown -R www-data:www-data storage bootstrap/cache

Production-Optimized Laravel (Multi-Stage)

Multi-stage builds keep your final image small by discarding build tools:

# Stage 1: Install PHP dependencies
FROM composer:2.7 AS deps
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
    --no-dev \
    --no-scripts \
    --prefer-dist \
    --optimize-autoloader

# Stage 2: Build frontend assets
FROM node:20-alpine AS assets
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY resources ./resources
COPY vite.config.js ./
RUN npm run build

# Stage 3: Production image
FROM cloudploy/php:8.3-fpm

# Copy only what production needs
COPY --from=deps /app/vendor ./vendor
COPY --from=assets /app/public/build ./public/build
COPY . .

RUN chown -R www-data:www-data storage bootstrap/cache \
 && php artisan config:cache \
 && php artisan route:cache \
 && php artisan view:cache

WordPress Dockerfile (Adding Extensions)

For WordPress sites needing custom PHP extensions:

FROM cloudploy/wordpress:6

# Install additional PHP extensions
RUN docker-php-ext-install \
    bcmath \
    intl

# Install PECL extensions
RUN pecl install imagick \
 && docker-php-ext-enable imagick

# Copy custom PHP configuration
COPY docker/php.ini /usr/local/etc/php/conf.d/99-custom.ini

# Copy application
COPY . /var/www/html/

Part 2: PHP-FPM Configuration

PHP-FPM manages a pool of PHP worker processes. Each incoming request is handled by one worker. If all workers are busy, new requests queue up. The right configuration depends on your server RAM and traffic pattern.

Default Pool Settings by Server Size

Setting 1GB RAM 4GB RAM 16GB RAM
pm.max_children 10 40 160
pm.start_servers 3 10 40
pm.min_spare_servers 2 5 20
pm.max_spare_servers 5 20 80
pm.max_requests 500 500 500

Each PHP-FPM worker uses roughly 30-80MB of RAM depending on your application. For a 4GB server running other services (Nginx, Redis, MySQL), assume 2-2.5GB available for PHP workers - giving you 30-80 workers at 30-80MB each.

To override the pool settings, add a custom configuration file in your Dockerfile:

COPY docker/php-fpm-pool.conf /usr/local/etc/php-fpm.d/www.conf

OPcache Configuration

OPcache precompiles PHP files into bytecode so they do not need to be parsed on every request. It is pre-enabled in CloudPloy base images. Recommended settings for production:

; /docker/php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.save_comments=1

Set validate_timestamps=0 in production (timestamps never change in an immutable Docker image). Set it to 1 on staging if you frequently update code without rebuilding the container.

Part 3: Environment Variables

Never bake sensitive values (database passwords, API keys, mail credentials) into your Docker image. CloudPloy injects these as environment variables at container startup from the secure environment variable store in your dashboard.

Add variables in App > Settings > Environment Variables. They are available to your application exactly as if they were in a .env file - getenv(), $_ENV, and Laravel's env() function all work.

For build-time variables (used during the Docker build, not at runtime), use Build Arguments instead. These are passed to docker build --build-arg and are not stored in the final image layer history if used correctly:

ARG COMPOSER_AUTH
RUN composer install --no-dev

Part 4: Volumes and Persistent Storage

Docker containers are stateless - any file written inside the container is lost when the container is replaced (during a new deployment, restart, or server migration). CloudPloy automatically mounts persistent volumes for the directories your application writes to:

Application Type Automatically Mounted Paths
WordPress /var/www/html/wp-content/uploads
Laravel /var/www/html/storage
Symfony /var/www/html/var

Add additional persistent paths in App > Settings > Volumes. Common additions include:

  • Log directories you want to persist across deployments
  • Generated export files (PDFs, CSV exports)
  • Shared media directories accessed by multiple workers

Part 5: Health Checks

CloudPloy polls your container's health endpoint every 30 seconds. If three consecutive checks fail, the container is restarted automatically. By default it checks GET / and expects a 2xx response.

For a more meaningful health check, add a dedicated endpoint and configure it in your Dockerfile:

HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD wget -qO- http://localhost/health || exit 1

And create the /health route in your application that verifies database and cache connectivity (see the Docker deployment tutorial for a full Laravel health endpoint example).

Part 6: Image Security

Run as Non-Root

By default, CloudPloy's base images run PHP-FPM as the www-data user (UID 33), not as root. Verify your Dockerfile preserves this:

# Check who runs the process - should NOT be root
USER www-data
CMD ["php-fpm", "-F"]

Minimize Image Size

Smaller images reduce attack surface, pull faster, and deploy quicker. The main levers:

  • Use Alpine-based images (php:8.3-fpm-alpine is ~30MB vs 400MB+ for Debian)
  • Use multi-stage builds to exclude build tools from the final image
  • Remove package manager caches: RUN apk add --no-cache ... or rm -rf /var/lib/apt/lists/*
  • Combine RUN commands with && to avoid extra layers

Avoid Secrets in Layers

Never put secrets in ENV or ARG instructions that get logged in the image build history. Instead, use CloudPloy's runtime environment variable injection or Docker BuildKit's secret mounts:

# Safe - secret is not stored in image layer
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=composer_auth \
    COMPOSER_AUTH=$(cat /run/secrets/composer_auth) \
    composer install --no-dev

Part 7: Debugging Container Issues

View Container Logs

From the CloudPloy dashboard go to App > Logs. Build logs show the Docker build output; application logs show PHP-FPM stdout/stderr and your application's log output.

Interactive Terminal

Open a shell inside your running container from App > Terminal. Useful for running one-off commands:

# Examples of commands in the container terminal
php artisan tinker
php artisan migrate:status
php -m                          # List installed PHP extensions
php --version
cat /usr/local/etc/php/php.ini  # Check PHP configuration

Check Installed PHP Extensions

If your application throws "Call to undefined function" errors for a PHP function, the required extension may not be installed in the base image. Check from the terminal:

php -m | grep -i redis
php -m | grep -i imagick
php -r "phpinfo();" | grep -i gd

If it is missing, add it to your Dockerfile with docker-php-ext-install or pecl install.

Frequently Asked Questions

Do I need Docker knowledge to use CloudPloy?

No. If you do not include a Dockerfile, CloudPloy handles everything automatically using its default base images. You only need to write a Dockerfile if you need custom PHP extensions, a specific base image, or build-time steps not covered by the defaults.

Can I use Docker Compose?

CloudPloy does not use docker-compose.yml - it manages orchestration through its own dashboard and API. Multi-container setups (workers, schedulers) are configured in App > Workers. The advantage is that CloudPloy handles health checks, restarts, and scaling automatically.

How do I update PHP version for an existing app?

Update the FROM line in your Dockerfile to the new PHP version base image, test on staging, then deploy. If you are not using a Dockerfile, change the PHP version in App > Settings > Runtime and trigger a new deployment.

Can I use a pre-built image from Docker Hub or GitHub Container Registry?

Yes. Connect your private registry in Server > Container Registry. CloudPloy will pull your pre-built image instead of building from source - useful for large monorepos or teams with existing CI pipelines that build and push images.

What happens to my container during a deployment?

CloudPloy uses a blue-green strategy: the new container is built and started, its health check is verified, then traffic is atomically switched from the old container to the new one. The old container is stopped after a 30-second drain period. Total downtime is zero for healthy deployments.


Questions about your container configuration? Read the Docker deployment tutorial for step-by-step examples, or contact CloudPloy support.