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-alpineis ~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 ...orrm -rf /var/lib/apt/lists/* - Combine
RUNcommands 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.