Webkul
Services
Technologies Bagisto QloApps
iOS Android SAP BigCommerce Wix
Trusted by
Samsung Intel Toyota Walmart Disney Nokia Nestlé Decathlon
See all customers
Back to Top
DEV

Setup for PimCore Development Environment

Docker-based environment for a fresh Pimcore installation.

Directory Structure

├── app
├── docker
│   ├── data
│   ├── nginx
│   │   ├── certs
│   │   │   ├── pimcore.local.crt
│   │   │   └── pimcore.local.key
│   │   └── default.conf
│   ├── php
│   │   ├── Dockerfile
│   │   └── pimcore-supervisor.conf
│   └── php.ini
└── docker-compose.yml

Services

  • php (pimcore_php) — Custom Dockerfile — PHP 8.4-FPM + Supervisord workers
  • nginx (pimcore_nginx) — nginx:stable — HTTPS reverse proxy
  • db (pimcore_db) — mysql:8.0 — Application database
  • redis (pimcore_redis) — redis:7-alpine — Cache / session (2 GB LRU)
  • rabbitmq (pimcore_rabbitmq) — rabbitmq:3-management — Message broker
  • opensearch (pimcore_opensearch) — opensearchproject/opensearch:2.11.0 — Full-text search
  • mercure (pimcore_mercure) — dunglas/mercure — Real-time SSE hub

All containers share a bridge network named pimcore. Service hostnames (db, redis, etc.) resolve automatically within it.

Ports

  • 443 — Nginx — Pimcore HTTPS
  • 5672 — RabbitMQ — AMQP
  • 15672 — RabbitMQ — Management UI (guest / guest)
  • 9201 — OpenSearch — REST API

Configuration Files

docker-compose.yml

services:
  php:
    build:
      context: ./docker
      dockerfile: php/Dockerfile
    container_name: pimcore_php
    command: >
      bash -c "
      mkdir -p /var/www/html/var /var/www/html/public/var &&
      chown -R www-data:www-data /var/www/html/var /var/www/html/public/var &&
      chmod -R 775 /var/www/html/var /var/www/html/public/var &&
      /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf
      "
    volumes:
      - ./app:/var/www/html
      - ./docker/php.ini:/usr/local/etc/php/php.ini:ro
      - ./app/.docker/supervisord.conf:/etc/supervisor/conf.d/pimcore.conf:ro
    depends_on:
      - db
      - redis
      - opensearch
    environment:
      - APP_ENV=prod
    networks:
      - pimcore

  nginx:
    image: nginx:stable
    container_name: pimcore_nginx
    ports:
      - "443:443"
    volumes:
      - ./app:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./docker/nginx/certs:/etc/nginx/certs:ro
    depends_on:
      - php
    networks:
      - pimcore

  db:
    image: mysql:8.0
    container_name: pimcore_db
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: pimcore
      MYSQL_USER: pimcore
      MYSQL_PASSWORD: pimcore
    volumes:
      - ./docker/data/mysql:/var/lib/mysql
    networks:
      - pimcore

  redis:
    image: redis:7-alpine
    container_name: pimcore_redis
    command: redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru
    volumes:
      - ./docker/data/redis:/data
    networks:
      - pimcore

  rabbitmq:
    image: rabbitmq:3-management
    container_name: pimcore_rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    networks:
      - pimcore

  opensearch:
    image: opensearchproject/opensearch:2.11.0
    container_name: pimcore_opensearch
    environment:
      discovery.type: single-node
      plugins.security.disabled: "true"
      OPENSEARCH_JAVA_OPTS: "-Xms2g -Xmx2g"
    volumes:
      - ./docker/data/opensearch:/usr/share/opensearch/data
    ports:
      - "9201:9200"
    networks:
      - pimcore

  mercure:
    image: dunglas/mercure:latest
    container_name: pimcore_mercure
    environment:
      SERVER_NAME: ":80"
      MERCURE_PUBLISHER_JWT_KEY: "YOUR_JWT_SECRET_KEY"
      MERCURE_SUBSCRIBER_JWT_KEY: "YOUR_JWT_SECRET_KEY"
      MERCURE_EXTRA_DIRECTIVES: |
        anonymous
        cors_origins *
    networks:
      - pimcore

networks:
  pimcore:

Change YOUR_JWT_SECRET_KEY to a long random string — must match MERCURE_JWT_KEY in app/.env.local.

docker/php/Dockerfile

FROM php:8.4-fpm

# System dependencies + supervisor (queue worker manager) + graphviz (Pimcore workflow diagrams)
# libwebp-dev: WebP image support for GD (product images may be WebP)
RUN apt-get update && apt-get install -y --no-install-recommends \
    git unzip libicu-dev libzip-dev libpng-dev libjpeg-dev libfreetype6-dev \
    libwebp-dev \
    libonig-dev libxml2-dev libxslt1-dev libcurl4-openssl-dev \
    libpq-dev libssl-dev librabbitmq-dev \
    graphviz \
    supervisor \
    && rm -rf /var/lib/apt/lists/*

# PHP extensions — configure GD with JPEG + WebP + FreeType before installing
RUN docker-php-ext-configure gd --with-jpeg --with-webp --with-freetype \
    && docker-php-ext-install \
    pdo pdo_mysql intl zip gd opcache bcmath soap xsl exif

# Redis extension
RUN pecl install redis && docker-php-ext-enable redis

# AMQP extension
RUN pecl install amqp && docker-php-ext-enable amqp

# Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

# Opcache config
COPY php.ini /usr/local/etc/php/php.ini

# Supervisord config — manages php-fpm + all Pimcore queue workers
COPY php/pimcore-supervisor.conf /etc/supervisor/conf.d/pimcore.conf

WORKDIR /var/www/html

# Wrapper: always run bin/console as www-data so var/ files get correct ownership
RUN printf '#!/bin/sh\nexec su -s /bin/sh www-data -c "php /var/www/html/bin/console $*"\n' \
    > /usr/local/bin/pimcore && chmod +x /usr/local/bin/pimcore

# supervisord runs in foreground and manages php-fpm + workers
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]

docker/php.ini

memory_limit=2G
max_execution_time=300
upload_max_filesize=512M
post_max_size=600M

opcache.enable=1
opcache.memory_consumption=512
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0

realpath_cache_size=4096k
realpath_cache_ttl=600
Set opcache.validate_timestamps=1 in development so code changes apply without a container restart.

docker/php/pimcore-supervisor.conf

# Supervisord program definitions for Pimcore PHP container
# supervisord is started as the container entrypoint; it manages php-fpm and all queue workers.

[program:php-fpm]
command=/usr/local/sbin/php-fpm --nodaemonize
autostart=true
autorestart=true
priority=1
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

[program:messenger-consume]
command=php /var/www/html/bin/console messenger:consume pimcore_generic_data_index_queue scheduler_generic_data_index pimcore_core pimcore_maintenance pimcore_scheduled_tasks pimcore_image_optimize --memory-limit=250M --time-limit=3600
numprocs=1
startsecs=0
autostart=true
autorestart=true
user=www-data
priority=10
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

[program:consume-asset-update]
command=php /var/www/html/bin/console messenger:consume pimcore_asset_update --memory-limit=250M --time-limit=3600
numprocs=1
startsecs=0
autostart=true
autorestart=true
user=www-data
priority=10
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

[program:consume-execution-engine]
command=php /var/www/html/bin/console messenger:consume pimcore_generic_execution_engine --memory-limit=250M --time-limit=3600
numprocs=1
startsecs=0
autostart=true
autorestart=true
user=www-data
priority=10
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

[program:maintenance]
command=bash -c 'sleep 3600 && exec php /var/www/html/bin/console pimcore:maintenance'
autostart=true
autorestart=true
user=www-data
priority=10
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

docker/nginx/default.conf

server {
    listen 80;
    server_name pimcore.local;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name pimcore.local;

    ssl_certificate /etc/nginx/certs/pimcore.local.crt;
    ssl_certificate_key /etc/nginx/certs/pimcore.local.key;

    root /var/www/html/public;
    index index.php;

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
    gzip_min_length 1000;
    gzip_comp_level 4;
    gzip_vary on;
    gzip_proxied any;

    # Mercure SSE hub proxy — handles both /hub (browser SSE) and /hub/.well-known/mercure (PHP publisher)
    location /hub {
        rewrite ^/hub(.*)$ /.well-known/mercure break;
        proxy_pass http://mercure;
        proxy_read_timeout 24h;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Connection "";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding on;
    }

    location = /admin {
        return 302 /pimcore-studio/login;
    }

    location = /admin/ {
        return 302 /pimcore-studio/login;
    }

    # Pimcore thumbnail routing: /6/image-thumb__6__name/file.jpg
    # Studio generates URLs without the /var/tmp/thumbnails/ prefix.
    # Remap the path to the real location; fall back to PHP to generate missing thumbnails.
    location ~ "^/[0-9]+/image-thumb__[0-9]+__" {
        try_files /var/tmp/thumbnails$uri /index.php$is_args$args;
        expires max;
        log_not_found off;
    }

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~* ^/catalog-import/ {
        client_max_body_size 600m;
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
        client_max_body_size 600m;
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS on;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_read_timeout 300;
    }

    # Block direct access to PHP files (security best practice)
    location ~ \.php$ {
        return 404;
    }

    location /pimcore-studio/public/review/ {
        alias /var/www/html/public/review/;
        autoindex on;
        try_files $uri $uri/ =404;
    }

    location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|ico|xml)$ {
        try_files $uri /index.php$is_args$args;
        expires max;
        log_not_found off;
    }
}

app/.env

Committed with safe placeholder values:

APP_ENV=dev
APP_DEBUG=true

APPLICATION_SECRET=CHANGE_ME_APPLICATION_SECRET_KEY_LONG_ENOUGH_FOR_VALIDATION

DATABASE_URL=mysql://pimcore:pimcore@db:3306/pimcore

PIMCORE_ADMIN_USER=admin
PIMCORE_ADMIN_PASSWORD=admin

PIMCORE_OPENSEARCH_DSN=opensearch://opensearch:9200

PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX=amqp://guest:guest@rabbitmq:5672/%2f/
RABBITMQ_DSN=amqp://guest:guest@rabbitmq:5672/%2f

MERCURE_JWT_KEY=CHANGE_ME_THIS_IS_MY_SECRET_KEY_THAT_IS_LONG_ENOUGH_FOR_VALIDATION
MERCURE_URL=http://localhost/hub
MERCURE_SERVER_URL=http://mercure/.well-known/mercure

app/.env.local

Created on the server only — never committed:

APP_ENV=prod
APP_DEBUG=false

# Generate with: openssl rand -hex 32
APPLICATION_SECRET=<your-random-secret>

PIMCORE_BASE_URL=https://YOUR_DOMAIN_OR_IP

# Must match MERCURE_PUBLISHER_JWT_KEY in docker-compose.yml
MERCURE_JWT_KEY=<same-key-as-docker-compose>

Fresh Instance Setup

1. Clone the repository

git clone <repo-url> pimcore-docker 
cd pimcore-docker

2. Create app/.env.local

Copy the template above and fill in real values.

3. Generate SSL certificates

mkdir -p docker/nginx/certs

openssl req \
  -x509 \
  -nodes \
  -days 3650 \
  -newkey rsa:2048 \
  -keyout docker/nginx/certs/pimcore.local.key \
  -out docker/nginx/certs/pimcore.local.crt \
  -subj "/CN=pimcore.local"

For a real domain, place your CA-signed .crt and .key in docker/nginx/certs/ and update default.conf.

4. Build and start containers

docker compose up -d --build

Wait ~30 seconds, then verify all services show Up:

docker compose ps

5. Install Composer dependencies

docker exec pimcore_php composer install --no-dev --optimize-autoloader --no-interaction -d /var/www/html

6. Install Pimcore (first run only)

docker exec pimcore_php pimcore pimcore:install --admin-username=admin --admin-password=admin --mysql-host-socket=db --mysql-database=pimcore --mysql-username=pimcore --mysql-password=pimcore --no-interaction

On subsequent deploys, run migrations instead:

docker exec pimcore_php pimcore doctrine:migrations:migrate --no-interaction

7. Install assets, warm cache, build index

docker exec -u www-data pimcore_php php /var/www/html/bin/console assets:install --symlink
docker exec -u www-data pimcore_php php /var/www/html/bin/console cache:warmup --env=prod
docker exec -u www-data pimcore_php php /var/www/html/bin/console generic-data-index:update-index-settings --all


8. Verify

  • https://YOUR_SERVER_IP/pimcore-studio/login — Pimcore admin login
  • http://YOUR_SERVER_IP:15672 — RabbitMQ UI (guest / guest)
  • http://YOUR_SERVER_IP:9201 — OpenSearch REST API

Common Commands

# View logs
docker compose logs -f

# View PHP container logs
docker compose logs -f php

# Run Pimcore console commands
docker exec pimcore_php pimcore <command>

# Shell access to PHP container
docker exec -it pimcore_php bash

# Clear cache
docker exec -u www-data pimcore_php php /var/www/html/bin/console cache:clear

# Restart PHP container/workers after a code change
docker compose restart php

# Reload Nginx without downtime
docker exec pimcore_nginx nginx -s reload

# Rebuild PHP image after Dockerfile changes
docker compose up -d --build php

# Stop all containers
docker compose down

# Wipe all persistent data (DESTRUCTIVE)
docker compose down
rm -rf docker/data/mysql
rm -rf docker/data/opensearch
rm -rf docker/data/redis

Troubleshooting

Containers won’t start — check logs:

docker compose up --build 
docker compose logs php

502 Bad Gateway — PHP may still be initialising. Check supervisor:

docker exec pimcore_php supervisorctl status

All programs should show RUNNING. If php-fpm shows FATAL:

docker exec pimcore_php supervisorctl tail php-fpm

Database refused — MySQL takes 20–30 s on first boot. Watch until “ready for connections”:

docker compose logs -f db

Permission errors on var/:

docker exec pimcore_php bash -c "chown -R www-data:www-data /var/www/html/var /var/www/html/public/var && chmod -R 775 /var/www/html/var /var/www/html/public/var"

OpenSearch index empty after import — check workers and queue:

docker exec pimcore_php supervisorctl status messenger-consume:* 
docker exec pimcore_php pimcore messenger:stats

The ./app directory is bind-mounted — PHP file edits take effect immediately. Dockerfile or supervisor config changes require docker compose up -d --build php.

. . .

Leave a Comment

Your email address will not be published. Required fields are marked*


Be the first to comment.

Back to Top

Message Sent!

If you have more details or questions, you can reply to the received confirmation email.

Back to Home