Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions upsun/10-sulu-upsun.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
requirements:
- type: file_exists
value: composer.json
- type: has_composer_package
value: sulu/sulu

template: |
{{- $varnishServiceName := "" -}}
{{- $hasRedisSession := false -}}
{{- range $service := $.Services -}}
{{- if eq $service.Type "varnish" -}}
{{- $varnishServiceName = $service.Name -}}
{{- end -}}
{{- if eq $service.Name "redissession" -}}
{{- $hasRedisSession = true -}}
{{- end -}}
{{- end -}}
routes:
{{- if $varnishServiceName }}
"https://{all}/admin": { type: upstream, upstream: "{{.Slug}}:http", cache: { enabled: false } }
"https://{all}/": { type: upstream, upstream: "{{ $varnishServiceName }}:http", cache: { enabled: false } }
{{- else }}
"https://{all}/": { type: upstream, upstream: "{{.Slug}}:http", cache: { enabled: false } }
{{- end }}
"http://{all}/": { type: redirect, to: "https://{all}/" }

services: {{- if not $.Services }} {}{{ end }}
{{- range $service := $.Services }}
{{ $service.Name }}:
type: {{ $service.Type }}{{ if $service.Version }}:{{ $service.Version }}{{ end }}
{{- if eq $service.Type "varnish" }}
relationships:
application: "{{$.Slug}}:http"
configuration:
vcl: !include
type: string
path: varnish.vcl
{{- end }}
{{- end }}

applications:
{{.Slug}}:
source:
root: "/"

type: php:{{.PhpVersion}}

runtime:
extensions:
{{- range $ext := $.PHPExtensions }}
{{- if php_extension_available $ext $.PhpVersion }}
- {{ $ext }}
{{- end }}
{{- end }}

variables:
php:
{{- if php_at_least "7.4" }}
opcache.preload: config/preload.php
{{- end }}
{{- if $hasRedisSession }}
session.save_handler: redis
session.save_path: "tcp://${REDISSESSION_HOST}:${REDISSESSION_PORT}"
{{- end }}

build:
flavor: none

web:
locations:
"/":
root: "{{.PublicDirectory}}"
passthru: "/{{.FrontController}}"
"/uploads":
root: "{{.PublicDirectory}}"
passthru: "/{{.FrontController}}"
expires: 30d
scripts: false
allow: true

mounts:
"/var/cache": { source: instance, source_path: var/cache }
"/var/share": { source: storage, source_path: var/share }
"/var/storage": { source: storage, source_path: var/storage }
"/var/indexes": { source: storage, source_path: var/indexes }
Comment on lines +84 to +85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not inside var/share now that the concept exists?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to discuss with @alexander-schranz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same would be true for storage, but I’m not entirely sure. Storage is something you need to back up as well. Within shared storage, there are items that can be restored from other sources, such as rebuilding the index or the app cache, right? /cc @alexander-schranz

Copy link

@alexander-schranz alexander-schranz Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make another suggestion in symfony, but currently waiting for symfony/symfony#62204 being merged, as it build ontop of that change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlocked :) now merged

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chirimoya lets discuss where we should store our data. The APP_SHARE_DIR env variable will be something like var/share the %kernel.share_dir% includes the env so var/share/%kernel.environment% so directory structure will be something like:

If we use %APP_SHARE_DIR% the directory structure will be (so kind of mixed):

 - var/share
    - dev
       - app_cache
       - http_cache
    - indexes
    - prod
       - app_cache
       - http_cache
    - stage
       - app_cache
       - http_cache
    - storage

If we use %kernel.share_dir% it will be:

 - var/share
    - dev
       - pools
       - http_cache
       - indexes
       - storage
    - stage
       - pools
       - http_cache
       - indexes
       - storage
    - prod
       - pools
       - http_cache
       - indexes
       - storage

Second one will make switch between APP_ENV hard if you want to debug a stage or prod issue, test http caching with media.

Copy link

@alexander-schranz alexander-schranz Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indexes and storage directory are related to the data inside the DATABASE which is in most cases not related to the kernel.environment (Sulu projects mostly use MySQL, MariaDB, PostgreSQL). Only SQLite (not official supported by Sulu) in the Symfony recipes includes the ENV here: https://github.com/symfony/recipes/blob/7845ebb7ec83ea88ec02f626a22142e964afb467/doctrine/doctrine-bundle/2.13/manifest.json#L13-L16

Sulu CMS uses that recipe files from Symfony, in Sylius the Ecommerce System they include the %kernel.environment% also in the database URL: https://github.com/Sylius/Sylius-Standard/blob/11061a4d3e24a5f358bc65860e17e5795f5423f7/.env#L13.

So in case for uploads where project is mysql, postgresql it make sense for me to use an own directory or the %APP_SHARE_DIR% ENV variables when Symfony / Sulu recipe is used.

If Sulu or even Symfony would go the way of include the %kernel.environment% then I would so we could also go for the indexes and storage with the %kernel.share_dir%.


What we should keep in mind environment specific sure make backup scripts harder as they would need to know in which ENV stage, prod the system is running.


My personally preferred directory structure would have been:

 - var/share
    - cache (makes clear this is just rebuild and can be removed safely)
      - dev
         - app_cache
         - http_cache
      - stage
         - app_cache
         - http_cache
      - prod
         - app_cache
         - http_cache
    - indexes (none env dir)
    - storage (none env dir)

It would still be possible via prepend extension and overrides in or own Kernel.php.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolas-grekas for now, we will keep the separate storage and index mount and might change it in the future once 7.4 is released. We will need to keep the template updated anyway. It would be great if we could merge this so that the linked Symfony CLI merge request can be merged as well: symfony-cli/symfony-cli#645

"/public/uploads": { source: storage, source_path: public/uploads }

{{ if $.Services -}}
relationships:
{{- range $service := $.Services }}
{{- if ne $service.Type "varnish" }}
{{ $service.Name }}: "{{ $service.Name }}:{{ with $service.Endpoint }}{{ . }}{{ else }}{{ $service.Type }}{{ end }}"
{{- end }}
{{- end }}

{{ end -}}

hooks:
build: |
set -x -e

curl -fs https://get.symfony.com/cloud/configurator | bash
{{ range $ext := $.PHPExtensions -}}
{{- if not (php_extension_available $ext $.PhpVersion) -}}
# php-ext-install {{ $ext }} X.Y.Z
{{ end -}}
{{- end }}
NODE_VERSION=22 symfony-build

deploy: |
set -x -e

symfony-deploy

crons:
security-check:
# Check that no security issues have been found for PHP packages deployed in production
spec: '50 23 * * *'
cmd: if [ "$PLATFORM_ENVIRONMENT_TYPE" = "production" ]; then croncape COMPOSER_ROOT_VERSION=1.0.0 COMPOSER_AUDIT_ABANDONED=ignore composer audit --no-cache; fi
clean-expired-sessions:
spec: '17,47 * * * *'
cmd: croncape php-session-clean

{{ if has_composer_package "symfony/messenger" -}}
workers:
messenger:
commands:
# Consume "async" messages (as configured in the routing section of config/packages/messenger.yaml)
start: symfony console --time-limit=3600 --memory-limit=64M messenger:consume async
{{- end }}

extra_files:
".upsun/varnish.vcl": |
# Varnish configuration for Sulu CMS on upsun
# This configuration handles cache invalidation and Sulu-specific requirements

import std;
import xkey;

acl invalidators {
"localhost";
# TODO add outbound IPs for the your region
}

sub vcl_recv {
# Define the backend
set req.backend_hint = application.backend();

if (req.method == "PURGE") {
if (!std.ip(req.http.X-Client-IP, "0.0.0.0") ~ invalidators) {
return (synth(405, "Not allowed"));
}

return (purge);
}

if (req.method == "PURGEKEYS") {
if (!std.ip(req.http.X-Client-IP, "0.0.0.0") ~ invalidators) {
return (synth(405, "Not allowed"));
}

# If neither of the headers are provided we return 400 to simplify detecting wrong configuration
if (!req.http.xkey-purge && !req.http.xkey-softpurge) {
return (synth(400, "Neither header XKey-Purge or XKey-SoftPurge set"));
}

# Based on provided header invalidate (purge) and/or expire (softpurge) the tagged content
set req.http.n-gone = 0;
set req.http.n-softgone = 0;
if (req.http.xkey-purge) {
set req.http.n-gone = xkey.purge(req.http.xkey-purge);
}

if (req.http.xkey-softpurge) {
set req.http.n-softgone = xkey.softpurge(req.http.xkey-softpurge);
}

return (synth(200, "Purged " + req.http.n-gone + " objects, expired " + req.http.n-softgone + " objects"));
}
}

sub vcl_backend_response {
# Set grace period
set beresp.grace = 2m;
set beresp.keep = 8m;

if (beresp.http.X-Reverse-Proxy-TTL) {
set beresp.ttl = std.duration(beresp.http.X-Reverse-Proxy-TTL + "s", 0s);
}
}

sub vcl_deliver {
# Add debug headers in development
if (resp.http.X-Cache-Debug) {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}

if (!resp.http.X-Cache-Debug) {
# Remove tag headers when delivering to non debug client
unset resp.http.xkey;
unset resp.http.X-Reverse-Proxy-TTL;
}
}
25 changes: 14 additions & 11 deletions upsun/15-symfony-flex-upsun.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ template: |
"https://{all}/": { type: upstream, upstream: "{{.Slug}}:http" }
"http://{all}/": { type: redirect, to: "https://{all}/" }

services: {{ if not $.Services -}}{}{{ end }}
{{ range $service := $.Services -}}
services: {{- if not $.Services }} {}{{ end }}
{{- range $service := $.Services }}
{{ $service.Name }}:
type: {{ $service.Type }}{{ if $service.Version }}:{{ $service.Version }}{{ end }}

{{ end }}
{{- end }}

applications:
{{.Slug}}:
Expand All @@ -25,17 +24,19 @@ template: |

runtime:
extensions:
{{ range $ext := $.PHPExtensions -}}
{{- if php_extension_available $ext $.PhpVersion -}}
{{- range $ext := $.PHPExtensions }}
{{- if php_extension_available $ext $.PhpVersion }}
- {{ $ext }}
{{ end -}}
{{- end }}
{{- end }}

{{ if php_at_least "7.4" -}}
variables:
php:
opcache.preload: config/preload.php
{{- end }}

{{ end -}}

build:
flavor: none

Expand All @@ -55,10 +56,12 @@ template: |

{{ if $.Services -}}
relationships:
{{ range $service := $.Services -}}
{{- range $service := $.Services }}
{{ $service.Name }}: "{{ $service.Name }}:{{ $service.Type }}"
{{ end -}}
{{- end }}
{{- end }}

{{ end -}}

hooks:
build: |
set -x -e
Expand Down