Skip to main content
Version: 2026.1

Customize Infrastructure Configuration

The template configuration files, which were installed by the previous step, need to be customized for your project. Please go through at least the files in the .platform/ folder and adapt them to your needs.

Configure your environments​

The default Symfony environment for Pimcore projects is dev, you may customize that for your PaaS environments accordingly.

  1. In the Pimcore PaaS Console, go to Settings -> Environments and select an environment, e.g. production.
  2. Open the tab Variables
  3. Create a new variable with the name env:APP_ENV and the value prod
  4. Pimcore now runs in production mode

Install Profile​

Pimcore PaaS ships with a built-in install profile (Pimcore\Paas\Install\PaasProfile) that automates the initial Pimcore installation on first deploy. The profile uses the new profile-based installer introduced in Pimcore 2026.x

What the profile configures​

The PaaS profile declares the following env var definitions, which the installer collects and validates:

DefinitionEnv VarPurpose
DatabaseEnvVarDefinitionDATABASE_URLMySQL/MariaDB connection DSN
OpenSearchEnvVarDefinitionPIMCORE_OPENSEARCH_DSNOpenSearch connection DSN
AmqpMessengerEnvVarDefinitionPIMCORE_MESSENGER_TRANSPORT_DSN_PREFIXRabbitMQ AMQP transport DSN prefix
MercureEnvVarDefinitionMERCURE_JWT_KEY, MERCURE_URL, MERCURE_SERVER_URLMercure real-time hub (on PaaS, JWT keys are derived from PIMCORE_ENCRYPTION_SECRET by the template)
ProductRegistrationEnvVarDefinitionPIMCORE_ENCRYPTION_SECRET, PIMCORE_INSTANCE_IDENTIFIER, PIMCORE_PRODUCT_KEYProduct registration and encryption
GotenbergEnvVarDefinitionGOTENBERG_BASE_URLGotenberg PDF service URL (optional)

The profile installs the following bundles during installation:

BundlePurpose
PimcoreGenericExecutionEngineBundleGeneric execution engine for background jobs
PimcoreCustomReportsBundleCustom reporting engine
PimcoreGenericDataIndexBundleGeneric data indexing
PimcoreStudioBackendBundleStudio backend API
PimcoreStudioUiBundleStudio UI frontend
PimcoreEnterpriseSubscriptionToolsBundleEnterprise subscription tools

Skipped install steps on PaaS​

The PaaS profile implements InstallStepFilterInterface to skip install steps that are already handled by the PaaS deployment pipeline:

Skipped StepReason
WriteEnvEnvironment variables are provided by the platform via PLATFORM_RELATIONSHIPS and startup.php — no .env.local file is needed
WriteDoctrineConfigDoctrine mapping types configuration is provided by the platform.yaml template
RegisterBundlesconfig/bundles.php is committed in the repository — the installer does not need to write to it
InstallAssetsAssets are installed during the build phase via assets:install in the build hook
RunMaintenanceMaintenance is handled by the pimcore_maintenance cron job

This eliminates the need for filesystem workarounds (symlinks, writable mounts for config files) that were previously required to allow the installer to write to the read-only deploy filesystem.

How it runs on PaaS​

During the first deployment, check-installation-status.sh runs:

php vendor/bin/pimcore-install --install-profile='Pimcore\Paas\Install\PaasProfile' --no-interaction

Since startup.php has already populated all env vars from PLATFORM_RELATIONSHIPS at this point, the installer runs non-interactively — it reads DATABASE_URL, PIMCORE_OPENSEARCH_DSN, PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX, etc. directly from the environment without prompting.

Using the profile locally​

You can use the same profile for local installation:

./vendor/bin/pimcore-install --install-profile='Pimcore\Paas\Install\PaasProfile'

The installer will interactively prompt for each service connection. To skip validation for services you don't run locally:

./vendor/bin/pimcore-install --install-profile='Pimcore\Paas\Install\PaasProfile' --skip-validation=gotenberg

Using a custom profile​

If your project needs additional env var definitions or bundles beyond what the PaaS profile provides, create your own profile class implementing InstallProfileInterface and reference it in .platform-scripts/deploy/check-installation-status.sh:

namespace App\Install;

use Pimcore\Bundle\InstallBundle\Profile\InstallProfileInterface;
use Pimcore\Bundle\InstallBundle\Profile\InstallStep;
use Pimcore\Bundle\InstallBundle\Profile\InstallStepFilterInterface;

final readonly class MyProjectProfile implements InstallProfileInterface, InstallStepFilterInterface
{
public function getSkippedInstallSteps(): array
{
return [
InstallStep::WriteEnv,
InstallStep::WriteDoctrineConfig,
InstallStep::RegisterBundles,
InstallStep::InstallAssets,
InstallStep::RunMaintenance,
];
}

// ... implement remaining InstallProfileInterface methods
}

Then update the deploy script:

php vendor/bin/pimcore-install --install-profile='App\Install\MyProjectProfile' --no-interaction

RabbitMQ​

RabbitMQ is configured as the default transport for Symfony messenger. If you would like to change that, you need to update the following files and remove the RabbitMQ config and dependencies:

  • config/pimcore/startup.php
  • config/packages/messenger.yaml
  • .platform/applications.yaml
  • .platform/services.yaml
  • composer.json

OpenSearch​

For starting a new project based on Enterprise demo, the default configuration on the Pimcore side is done for OpenSearch. If you are migrating your project and want to use OpenSearch, uncomment the following block in config/platform.yaml:

pimcore_open_search_client:
clients:
default:
dsn: '%env(PIMCORE_OPENSEARCH_DSN)%'

S3 buckets storage​

We support S3 buckets as storage. Default configuration is provided by Pimcore. If you would like to use S3 buckets as storage, you need to uncomment the assets_s3 service definition and the flysystem storage blocks in config/platform.yaml, and add the following variables to your project/environment in the Pimcore PaaS Console:

  • env:S3_STORAGE_KEY - with the access key
  • env:S3_STORAGE_SECRET- with the secret
  • env:S3_PRIVATE_BUCKET - with the name of the bucket which will contain private files
  • env:S3_PUBLIC_BUCKET - with the name of the bucket which will contain publicly available files

S3 bucket policies​

Your bucket policies could be configured as following:

  • Public files
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::id:user/username"
},
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:ReplicateObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::pimcore-public/*",
"arn:aws:s3:::pimcore-public"
]
},
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::pimcore-public/*"
}
]
}
  • Private files
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::id:user/username"
},
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:ReplicateObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::pimcore-private/*",
"arn:aws:s3:::pimcore-private"
]
}
]
}

You could also combine Cloudfront with your S3 config to optimize performance and costs of an S3 bucket. Check the guide

Fastly CLI​

The Fastly CLI can be installed into your application container on demand. This provides tooling only — it is not a Pimcore-side Fastly integration, so there is no automatic cache-tag purging or surrogate-key handling. You decide what to run and when.

Fastly sits in front of the PaaS router, so its behaviour composes with the per-route cache: settings in .upsun/includes/routes.yaml, which by default disable edge caching for / and enable it for /bundles/, /cache-buster- and /static/.

Enabling​

Set a single variable in the PaaS Console (Settings → Variables):

VariableValueNotes
env:ENABLE_FASTLY_CLI1Must be marked as available during build

The variable has to be build-visible because the CLI is installed by .platform-scripts/build/fastly-cli.sh during the build hook. A runtime-only ENABLE_FASTLY_CLI installs nothing — there is deliberately no deploy-phase fallback. Accepted truthy values are 1, true, TRUE, True, yes, YES, on and ON; anything else, including 0 and false, disables installation.

Redeploy after setting the variable. The build log will show Fastly CLI: checksum verified. followed by a line starting with Fastly CLI: installed v15.5.0 to /app/.fastly.

What gets installed​

PathContents
.fastly/libexec/fastlyThe CLI binary, pinned to a specific release and SHA256-verified before extraction
.fastly/bin/fastlyA small wrapper, placed on PATH by the root .environment file
var/fastly/The CLI's own config directory. Not created during build - /var/ is a storage mount unavailable at build time. The wrapper creates it lazily on its first invocation

The wrapper exists because the CLI writes its configuration to $XDG_CONFIG_HOME/fastly/config.toml and creates that directory on every invocation, while the application directory is read-only at runtime. The wrapper redirects it into the writable var/ mount without exporting XDG_CONFIG_HOME globally, which would also relocate Composer's home directory. Override the location with XDG_CONFIG_HOME_FASTLY if needed.

To pin a different release, set FASTLY_CLI_VERSION (e.g. v15.4.0) — like ENABLE_FASTLY_CLI, it is read by the build hook, so it must be marked as available during build or it has no effect. This bypasses the built-in checksum pin and falls back to the checksums published with that release, and the build log will warn you that it did so.

Usage​

The wrapper is on PATH in SSH sessions, and in any other context where .environment is sourced:

fastly version
fastly whoami
fastly purge --all
fastly service vcl snippet list --version=active

Upsun documents .environment as being sourced when the container starts and on SSH logins - not specifically in the deploy hook. Scripts that run in the deploy hook and must not depend on that should invoke the wrapper by its absolute path, "${PLATFORM_APP_DIR}/.fastly/bin/fastly", instead of relying on PATH. This is what the shipped fastly-vcl.sh sync script does.

Commands that talk to the API need FASTLY_API_TOKEN and FASTLY_SERVICE_ID, which the CLI reads from the environment. Add them as Console variables (FASTLY_API_TOKEN should be marked sensitive) or pass them per invocation.

Optional: syncing VCL snippets on deploy​

.platform-scripts/deploy/fastly-vcl.sh ships ready to use but is not called. It syncs config/fastly/<type>/*.vcl into your service and activates a new service version only when a snippet's content or priority actually changed.

To enable it:

  1. Rename config/fastly/recv/default.vcl.example to default.vcl and adapt it. Only *.vcl files are picked up, which is why the shipped example is inert.
  2. Uncomment the fastly-vcl.sh line in .platform-scripts/deploy.sh.
  3. Add FASTLY_API_TOKEN and FASTLY_SERVICE_ID as Console variables.

Naming follows the file layout: config/fastly/recv/default.vcl becomes the snippet pimcore_recv, and config/fastly/recv/mobile.20.vcl becomes pimcore_recv_mobile with priority 20 (the default priority is 100). Valid type directories are init, recv, hash, hit, miss, pass, fetch, error, deliver, log and none.

Warning — preview environments. Child environments inherit their parent's variables, so a feature branch would otherwise clone and activate a new version of your production Fastly service. The script therefore refuses to run unless PLATFORM_ENVIRONMENT_TYPE is production. Set FASTLY_ALLOW_NON_PRODUCTION=1 only on an environment that points at its own Fastly service.

Warning — maintenance mode. Enabling the sync couples deploy success to the Fastly API. Maintenance mode is lifted by deploy/complete.sh, which runs after deploy.sh (and therefore after fastly-vcl.sh) completes. If the sync fails part-way - e.g. an invalid or expired token, a network error, a snippet the API rejects, or the concurrent-change abort described below - deploy.sh exits under set -e and the site is left in maintenance mode until you redeploy successfully.

Misconfiguration is not in that category: a missing ENABLE_FASTLY_CLI, FASTLY_API_TOKEN or FASTLY_SERVICE_ID, and a CLI that was never installed because ENABLE_FASTLY_CLI wasn't build-visible, are all logged and skipped with a success status. Those leave the deploy - and maintenance mode - unaffected; the sync simply does not run.

Note — re-running the installer. ./vendor/bin/pimcore-paas-install-config -f (see Good to know) re-mirrors the entire template/ directory, including .platform-scripts/deploy.sh. This restores the commented-out fastly-vcl.sh line and silently disables the sync again, so re-apply the uncomment after running the installer with -f.

Note. The snippet sync script has not been verified against a live Fastly service. Its gating and snippet-naming logic have been tested, but the API interactions have not. Review it and try it on a non-production service first.

Known limitations of the sync​

Deletions are not reconciled. The sync only pushes local files to the service; it never removes anything. Deleting or renaming a .vcl file leaves the snippet it previously created active on the service, so after renaming mobile.vcl to tablet.vcl both pimcore_recv_mobile and pimcore_recv_tablet run. Remove the stale one manually:

fastly service vcl snippet delete --version=active --autoclone --name=pimcore_recv_mobile
fastly service version activate --version=latest

(--autoclone is required because the active version is locked: it clones the active version and applies the delete to the clone, which the activate then promotes.)

This is deliberate — reconciling deletions automatically would mean inferring destructive changes to a live CDN configuration from local filenames.

Two files must not map to the same snippet name. Because the priority segment is stripped when deriving the name, mobile.vcl and mobile.100.vcl both yield pimcore_recv_mobile. The script detects this before making any API call and fails the deploy naming both paths, rather than letting one silently overwrite the other.

A changed snippet type creates a new snippet. The type is part of the name, so moving recv/foo.vcl to deliver/foo.vcl creates pimcore_deliver_foo and leaves pimcore_recv_foo behind — the deletion caveat above applies.

Concurrent changes abort the sync. The whole run is pinned to the version that was active when it started: comparisons read from it, the working draft is cloned from it, and before activation the script verifies it is still the active version. If something else was activated in the meantime (an operator in the Fastly UI, another environment pointed at the same service), the sync aborts instead of activating — activating would silently revert the concurrent change. The prepared draft is left inactive and is harmless; redeploy to re-sync against the new active version. The Fastly API has no atomic conditional activation, so a small check-to-activate window remains — deployments of the same PaaS environment are already serialized by the platform, so this protects against out-of-band changes.

Sitemaps configuration​

By default sitemaps configs are commented out. If you would like to enable it, uncomment it within applications.yaml under mounts and web configuration.

DataHub Configuration​

ProductSup Export Cron Job​

Enable the DataHub ProductSup cron job to process exports regularly:

datahub_product_sup:
spec: "* * * * *"
commands:
start: "php $PLATFORM_APP_DIR/bin/console datahub:export:productsup -p 1 --only-queue-items"

The same applies to other processes like datahub:simple-rest:process-queue:

datahub:simple-rest:process-queue:
spec: "* * * * *"
commands:
start: "php $PLATFORM_APP_DIR/bin/console datahub:simple-rest:process-queue -p 1 --only-queue-items --batch-size=20"

Processes using this parallelization mechanism that should be considered:

  • pimcore:thumbnails:image
  • pimcore:thumbnails:video
  • ecommerce:indexservice:bootstrap
  • ecommerce:indexservice:process-preparation-queue
  • ecommerce:indexservice:process-update-queue
  • datahub:simple-rest:process-queue
  • datahub:data-importer:process-queue-parallel
  • datahub:export:productsup
  • data-sync:export:batch-export
  • data-sync:export:transfer-export-queue
  • data-sync:sync:deletions
  • data-sync:import:execute-import-task

IMPORTANT! It is possible that the list of commands which utilize parallelization is incomplete in the docs, so if you are experiencing low throughput on a command you should check whether -p is available.

Process Configuration (-p and --batch-size parameters):

  • Use default: -p 1 (single process)
  • Use default --batch-size=20 so that command processes items in groups of 20, allowing intermediate cleanup and resource release between batches
  • For higher throughput: increase to e.g. -p 2 depending on your export volume
  • Consider your container resources when increasing the process count as it can affect performance

Where applicable, consider using --main-process instead of -p 1. While -p 1 limits execution to a single worker while keeping the parallelization mechanism available, --main-process completely disables parallel execution.

Workflow Designer​

There is a limitation with workflows due to the functionality of the Symfony Workflow component. The component only supports Symfony Config, which prevents setting the configuration via LocationAwareConfigRepository. For more information, see the Pimcore documentation on configuration storage locations

We recommend to create workflows using the Workflow Designer locally and put the generated workflow configuration file(s) into the ie. '%kernel.project_dir%/config/workflows/workflows.yaml' and add them to Git. To do so the following change in the configuration is necessary: Add an import statement in the /config/config.yaml file:

imports:
- { resource: 'workflows/' }

Ensure that your workflow YAML file(s) is placed in the /config/workflows/ directory. By following these steps, the workflow configuration process should work smoothly.

Large asset uploads​

Several parameters influence the maximum size of assets you can upload. They operate at different layers (router vs. PHP), and the smallest limit will always win.

Upsun router limit​

  • max_request_size
    Default: 250 MB
    This is the maximum HTTP request size accepted by the Upsun router (mapped to nginx’s client_max_body_size).
    When you encounter a 413 Request Entity Too Large, this limit was exceeded.
    See: Upsun Docs

PHP limits​

  • upload_max_filesize
    Default: 2 MB
    Maximum size of a single uploaded file.

  • post_max_size
    Default: 8 MB
    Maximum size of the entire POST body, including all files, fields, and multipart overhead.

  • max_file_uploads
    Default: 20
    Maximum number of files allowed in one request.

Required hierarchy​

To avoid unexpected rejections at different layers, ensure: max_request_size > post_max_size > upload_max_filesize

About memory_limit​

memory_limit does not cap upload size directly, since PHP streams uploaded files to temporary storage on disk.
However, PHP still needs sufficient memory to parse the POST body and populate $_POST/$_FILES.

Because post_max_size defines the largest payload PHP will process, memory_limit should always be greater than post_max_size to prevent fatal “Allowed memory size exhausted” errors.

How to override PHP limits on PaaS​

You can override PHP settings in .platform/applications.yaml under the variables.php section of your app definition (e.g., pimcore:). For example, to allow uploading files up to 256 MB:

pimcore:
variables:
php:
upload_max_filesize: 256M
post_max_size: 300M
memory_limit: 512M
max_file_uploads: 50