Production Docker template for OctoberCMS v4 projects.
The repository contains infrastructure files only. Install OctoberCMS in your project first, then copy this kit into the project root.
appDocker target based onphp:8.4-fpm-bookwormnginxDocker target based onnginx:1.27-alpine- PHP-FPM, queue worker, scheduler and Redis services
- Optional PostgreSQL service for single-server or staging deployments
- Composer authentication through BuildKit secrets
- Production PHP, OPcache and Nginx configuration
- Nginx hardening for root-based OctoberCMS deployments without requiring
public/mirror - Healthchecks, Docker log rotation and a deploy helper script
- Optional single-server blue-green/fallback web stack to reduce deploy 502 windows
- Installing OctoberCMS With This Kit
- Production Deployment
- Runtime Secrets
- Backup And Restore
- Optional S3 Backup Uploads
- Rollback
- Operations
- Updating The Docker Kit
- CI/CD Notes
- Bitbucket Pipelines Deployment
- GitLab CI/CD Deployment
- Debian 12 VPS Deployment
- Orchestrator Deploy Notes
- Project TODO
Create or open an OctoberCMS v4 project:
composer create-project october/october my-site
cd my-site
php artisan october:install
php artisan october:migrateCopy this kit into the project root:
git clone https://github.com/m49n/docker_october.git /tmp/docker_october
rsync -av --exclude=".git" /tmp/docker_october/ ./Create runtime environment:
cp .env.example .env
php artisan key:generateCreate local Composer auth file from the example:
cp auth.json.example auth.jsonEdit auth.json and set the OctoberCMS account email and license key. Never commit the real auth.json.
Build images:
DOCKER_BUILDKIT=1 docker build \
--secret id=composer_auth,src=auth.json \
--target app \
-t october-app:test .
DOCKER_BUILDKIT=1 docker build \
--target nginx \
-t october-nginx:test .Run production compose with locally built images:
APP_IMAGE=october-app NGINX_IMAGE=october-nginx IMAGE_TAG=test \
docker compose -f docker-compose.prod.yml up -dRun migrations as an explicit deploy step:
docker compose -f docker-compose.prod.yml run --rm php-fpm php artisan october:migrate --forceOr run the deploy helper after images are built:
DEPLOY_PULL=0 USE_LOCAL_DB=1 ./scripts/deploy.shUse a CI secret named COMPOSER_AUTH containing the JSON from auth.json.example.
docker build \
--secret id=composer_auth,env=COMPOSER_AUTH \
--target app \
-t registry.example.com/project/october-app:$IMAGE_TAG .
docker build \
--target nginx \
-t registry.example.com/project/october-nginx:$IMAGE_TAG .
docker push registry.example.com/project/october-app:$IMAGE_TAG
docker push registry.example.com/project/october-nginx:$IMAGE_TAGDeploy:
export IMAGE_TAG=2026-05-22-001
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml run --rm php-fpm php artisan october:migrate --forcenginx: public HTTP entrypointphp-fpm: web PHP runtimequeue: scalable queue workerscheduler: single scheduler container, do not scale this serviceredis: cache, sessions and queuespostgres: optional profile, enable with--profile local-db
Run with optional PostgreSQL:
docker compose -f docker-compose.prod.yml --profile local-db up -dWith this profile enabled, PostgreSQL runs as a separate container in the same Docker network. All scaled app containers connect to the same database service by using the service name as the host:
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=october
DB_USERNAME=october
DB_PASSWORD=change-meThis works for php-fpm, queue and scheduler, including when php-fpm or queue are scaled:
docker compose -f docker-compose.prod.yml --profile local-db up -d --scale php-fpm=3 --scale queue=3Use the bundled postgres service for a single-server deployment, staging or demos. For multi-server production, use an external PostgreSQL server or managed database and set DB_HOST to that external host instead.
The shared storage-app volume is mounted into php-fpm, queue, scheduler and read-only into nginx. This lets nginx serve local public media paths such as /storage/app/media on a single server. For multi-host production, use S3 or MinIO instead of local storage.
The repository includes scripts/deploy.sh for single-host Docker Compose deployments:
chmod +x scripts/deploy.sh
./scripts/deploy.shUseful options:
DEPLOY_PULL=0 ./scripts/deploy.sh # use locally built images
USE_LOCAL_DB=1 ./scripts/deploy.sh # include the bundled postgres profile
RUN_LARAVEL_MIGRATIONS=1 ./scripts/deploy.sh
RUN_OPTIMIZE=1 ./scripts/deploy.shThe script starts infrastructure services, runs october:migrate --force, optionally runs Laravel migrations and artisan optimize, signals queue and scheduler workers, then updates containers.
For single-server deployments behind Caddy, DEPLOY_BLUE_GREEN_ENABLED=1 starts a second nginx/php-fpm web stack on 127.0.0.1:8081, waits for healthchecks and smoke-checks it before recreating the primary 8080 stack. Configure Caddy to serve normal traffic from 8080 and use 8081 only as a handle_errors fallback; see Production Deployment.
Switch back to a previously built image tag:
USE_LOCAL_DB=1 ./scripts/rollback.sh <previous-image-tag>Rollback updates IMAGE_TAG, recreates services and waits for php-fpm and nginx. It does not roll back database migrations or content. See Rollback.
Create a backup:
BACKUP_DIR=/var/backups/october USE_LOCAL_DB=1 ./scripts/backup.shInstall the daily backup timer:
BACKUP_DIR=/var/backups/october ./scripts/install-backup-timer.shWith the default /etc/systemd/system target the installer uses sudo for systemd writes and systemctl. The generated service still runs as the current user unless BACKUP_USER and BACKUP_GROUP are set.
Optional S3 uploads and Telegram backup notifications are configured through /etc/october-backup.env. See Optional S3 Backup Uploads.
Preview old Docker image cleanup:
IMAGE_KEEP_COUNT=5 ./scripts/prune-images.shApply cleanup:
IMAGE_KEEP_COUNT=5 IMAGE_PRUNE_DRY_RUN=0 ./scripts/prune-images.shSee Operations for common production commands.
When this template receives global improvements, update client projects with:
./scripts/update-kit.sh
git diff
git commit -m "Update production Docker kit"See Updating The Docker Kit for safe defaults and overwrite options.
- Do not commit
.envorauth.json. - Test restore before treating the deployment as production-ready. See Backup And Restore.
- Treat
.env.exampleas the runtime configuration contract, not as a requirement to store production secrets in a file. - For a simple single-server VPS, a server-side
.envfile is acceptable when permissions are restricted withchmod 600 .env. - For orchestrators such as Kubernetes, BeCloud-like platforms or Docker Swarm, store runtime values in platform Secrets/ConfigMaps or Vault and inject them into containers at runtime.
- Do not run
composer installwhen a container starts. - Do not store
vendorin a Docker volume. - Do not run scheduler in every web container.
- Do not run migrations automatically in every container.
- Use Redis for cache, sessions and queue in multi-container deployments.
- Use S3 or MinIO for media when running more than one host. A named Docker volume is acceptable only for a single-server deployment.
- Default PHP limits are conservative:
memory_limit=128M,upload_max_filesize=32M,post_max_size=40M. Raise them per project when imports, media handling or heavy backend operations need more headroom. - Nginx keeps
root /var/www/htmlfor compatibility, but denies root-sensitive files and internal October/Laravel directories. For stricter deployments, migrate projects to October'spublic/mirror model separately.
After copying the kit into a real OctoberCMS project:
docker run --rm october-app:test php -m
docker run --rm october-app:test composer dump-autoload --no-dev --optimize
docker run --rm --env-file .env october-app:test php artisan list
docker run --rm october-nginx:test nginx -tThe PHP extension list should include pdo_pgsql, pgsql, redis, rdkafka, opcache, intl, zip, gd, bcmath, soap, curl, mbstring and SimpleXML.