From dc08bc621287ef9bf3bb78f3d0d2e8bd6bd89862 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Wed, 5 Aug 2026 18:34:09 +0200 Subject: [PATCH] fix(magento): make nginx and php-fpm actually serve The role could not deliver anything. The markoshust nginx image ships a default vhost built for a different topology: fastcgi points at unix:/sock/phpfpm.sock, port 8000 only returns 301 to https, the real server sits on 8443 with a self-signed cert, and it references an upstream host 'phpfpm' that does not exist here - so nginx aborted at config load with 'host not found in upstream' and the container never started. php-fpm listened on that same unix socket, because its /usr/local/etc/php-fpm.conf carries no include line and php-fpm.d/*.conf is therefore dead config. Both are now replaced through type: config mounts. The role never had any - that absence is why every template it previously rendered was inert. The vhost stays minimal and includes Magento's own nginx.conf, copied from nginx.conf.sample by the bootstrap. Its location blocks carry the .php whitelist, the /static/version rewrites and the /media to get.php fallback that a hand-written vhost has to reimplement - the previous attempt did not, and would have executed every .php under pub/. PHP moves from 8.5 to 8.4: Magento 2.4.8 requires ~8.2.0||~8.3.0||~8.4.0 and the bootstrap passes no platform-requirement override, so composer create-project failed before nginx ever mattered. nginx waits for the php socket before starting, for the reason given in the erpnext frontend commit: the wildcard search domain turns a DNS miss into a routable wrong answer, and nginx pins upstreams at config load. Also: the oauth2 origin port named 8080, where nothing listens, and the healthcheck proved only that some listener existed - it stayed green on a redirect loop and on 502. Measured in a rebuild of the role topology against Magento's real nginx.conf.sample: HTTP 200 on / and /index.php, healthy healthcheck, and the counter-test - without the gate and with php down, nginx exits 1. Co-Authored-By: Claude Opus 5 (1M context) --- roles/web-app-magento/meta/services.yml | 4 ++-- roles/web-app-magento/meta/volumes.yml | 16 ++++++++++++++++ roles/web-app-magento/tasks/00_core.yml | 12 ++++++++++++ roles/web-app-magento/tasks/01_setup.yml | 16 ++++++++++++++++ roles/web-app-magento/templates/compose.yml.j2 | 7 +++---- .../templates/nginx-default.conf.j2 | 13 +++++++++++++ roles/web-app-magento/templates/php-fpm.conf.j2 | 17 +++++++++++++++++ roles/web-app-magento/vars/main.yml | 7 +++++++ 8 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 roles/web-app-magento/templates/nginx-default.conf.j2 create mode 100644 roles/web-app-magento/templates/php-fpm.conf.j2 diff --git a/roles/web-app-magento/meta/services.yml b/roles/web-app-magento/meta/services.yml index 43200a3799..f652c8e576 100644 --- a/roles/web-app-magento/meta/services.yml +++ b/roles/web-app-magento/meta/services.yml @@ -14,7 +14,7 @@ sso: oauth2: origin: host: nginx - port: "8080" + port: "{{ lookup('config', 'web-app-magento', 'services.magento.ports.internal.http') }}" dashboard: bond: 1 enabled: "{{ 'web-app-dashboard' in group_names }}" @@ -29,7 +29,7 @@ email: shared: "{{ 'web-app-mailu' in group_names }}" php: image: markoshust/magento-php - version: 8.5-fpm + version: 8.4-fpm name: magento-php ports: internal: diff --git a/roles/web-app-magento/meta/volumes.yml b/roles/web-app-magento/meta/volumes.yml index 935958829f..34ed408fb2 100644 --- a/roles/web-app-magento/meta/volumes.yml +++ b/roles/web-app-magento/meta/volumes.yml @@ -7,3 +7,19 @@ data: target: /var/www/html - service: php target: /var/www/html + +nginx_conf: + type: config + source: "{{ [ lookup('container', 'web-app-magento', 'directories.volumes'), 'nginx-default.conf' ] | path_join }}" + mode: "0444" + mounts: + - service: nginx + target: /etc/nginx/conf.d/default.conf + +php_fpm_conf: + type: config + source: "{{ [ lookup('container', 'web-app-magento', 'directories.volumes'), 'php-fpm.conf' ] | path_join }}" + mode: "0444" + mounts: + - service: php + target: /usr/local/etc/php-fpm.conf diff --git a/roles/web-app-magento/tasks/00_core.yml b/roles/web-app-magento/tasks/00_core.yml index a18ab9d917..903a2a585c 100644 --- a/roles/web-app-magento/tasks/00_core.yml +++ b/roles/web-app-magento/tasks/00_core.yml @@ -1,4 +1,16 @@ --- +- name: "📄 Render the container config overrides for {{ application_id }}" + ansible.builtin.include_tasks: + file: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml') }}" + vars: + role_templates: + - src: "nginx-default.conf.j2" + dest: "{{ MAGENTO_NGINX_CONF_PATH }}" + mode: "0444" + - src: "php-fpm.conf.j2" + dest: "{{ MAGENTO_PHP_FPM_CONF_PATH }}" + mode: "0444" + - name: "🧩 load docker, db/redis/proxy for {{ application_id }}" include_role: name: sys-stk-full diff --git a/roles/web-app-magento/tasks/01_setup.yml b/roles/web-app-magento/tasks/01_setup.yml index 1501dea22b..b59b7ce01a 100644 --- a/roles/web-app-magento/tasks/01_setup.yml +++ b/roles/web-app-magento/tasks/01_setup.yml @@ -17,6 +17,7 @@ mkdir -p /tmp/composer/cache cd /var/www/html composer create-project --no-interaction --no-progress --repository-url=https://repo.magento.com/ magento/project-community-edition=2.4.8 . + cp nginx.conf.sample nginx.conf mkdir -p var pub/static pub/media app/etc chmod -R 775 var pub/static pub/media app/etc ' @@ -58,3 +59,18 @@ (magento_install.stdout is defined and ('Magento installation complete' in magento_install.stdout or 'successfully installed' in magento_install.stdout)) + +- name: "🔄 Restart nginx so it picks up Magento's vhost (compose)" + ansible.builtin.command: "container restart {{ MAGENTO_NGINX_CONTAINER | quote }}" + register: magento_nginx_restart_compose + changed_when: magento_nginx_restart_compose.rc == 0 + when: DEPLOYMENT_MODE == 'compose' + +- name: "🔄 Restart nginx so it picks up Magento's vhost (swarm)" + ansible.builtin.command: >- + container service update --force + {{ lookup('container_service', application_id, 'nginx') | quote }} + register: magento_nginx_restart_swarm + changed_when: magento_nginx_restart_swarm.rc == 0 + timeout: "{{ lookup('timeout', 600) | int }}" + when: DEPLOYMENT_MODE == 'swarm' diff --git a/roles/web-app-magento/templates/compose.yml.j2 b/roles/web-app-magento/templates/compose.yml.j2 index 562c77f86c..dc74d857d3 100644 --- a/roles/web-app-magento/templates/compose.yml.j2 +++ b/roles/web-app-magento/templates/compose.yml.j2 @@ -1,17 +1,16 @@ {% include 'roles/sys-svc-compose/templates/base.yml.j2' %} +{% set MAGENTO_PHP_READY_GATE = "until nc -z " ~ MAGENTO_PHP_HOST ~ " " ~ MAGENTO_PHP_PORT ~ " 2>/dev/null; do sleep 2; done;" %} {% set service_name = 'nginx' %} {{ service_name }}: {% include 'roles/sys-svc-container/templates/deploy.yml.j2' %} {{ lookup('container_image', application_id, 'nginx') }} {{ lookup('compose_only', 'container_name', MAGENTO_NGINX_CONTAINER) }} - environment: - PHP_HOST: "php" - PHP_PORT: "9000" + command: ["sh", "-c", "{{ MAGENTO_PHP_READY_GATE }} exec nginx -g 'daemon off;'"] {{ lookup('depends_on', ['php', 'search']) }} {{ lookup('container_volumes', application_id, 'nginx') | indent(4) }} {{ lookup('container_ports', ['magento', 'http', DOCKER_BIND_HOST]) | indent(4) }} healthcheck: - test: ["CMD-SHELL", "nginx -t >/dev/null 2>&1 && { grep -q ':1F40' /proc/net/tcp || grep -q ':1F40' /proc/net/tcp6; }"] + test: ["CMD-SHELL", "nginx -t >/dev/null 2>&1 && nc -z {{ MAGENTO_PHP_HOST }} {{ MAGENTO_PHP_PORT }}"] interval: 10s timeout: 5s retries: 5 diff --git a/roles/web-app-magento/templates/nginx-default.conf.j2 b/roles/web-app-magento/templates/nginx-default.conf.j2 new file mode 100644 index 0000000000..667452e7ef --- /dev/null +++ b/roles/web-app-magento/templates/nginx-default.conf.j2 @@ -0,0 +1,13 @@ +upstream fastcgi_backend { + server {{ MAGENTO_PHP_HOST }}:{{ MAGENTO_PHP_PORT }}; +} + +server { + listen {{ MAGENTO_NGINX_PORT }}; + + set $MAGE_ROOT /var/www/html; + + {# Exception: glob, not a typo — a hard include emergs on first boot, + before the bootstrap has copied nginx.conf into the volume. #} + include /var/www/html/nginx[.]conf; +} diff --git a/roles/web-app-magento/templates/php-fpm.conf.j2 b/roles/web-app-magento/templates/php-fpm.conf.j2 new file mode 100644 index 0000000000..b990ade4f6 --- /dev/null +++ b/roles/web-app-magento/templates/php-fpm.conf.j2 @@ -0,0 +1,17 @@ +{# Exception: this file has no include= line, so php-fpm.d/*.conf is never + read — an override dropped there is silently inert. #} +[global] +error_log = /proc/self/fd/2 +daemonize = no + +[www] +access.log = /proc/self/fd/2 +listen = 0.0.0.0:{{ MAGENTO_PHP_PORT }} +pm = dynamic +pm.max_children = 10 +pm.start_servers = 4 +pm.min_spare_servers = 2 +pm.max_spare_servers = 6 +clear_env = no +catch_workers_output = yes +process.dumpable = yes diff --git a/roles/web-app-magento/vars/main.yml b/roles/web-app-magento/vars/main.yml index 93022b14e0..582ad7dc92 100644 --- a/roles/web-app-magento/vars/main.yml +++ b/roles/web-app-magento/vars/main.yml @@ -7,6 +7,13 @@ MAGENTO_USER: "app" MAGENTO_DOMAIN: "{{ lookup('domain',application_id) }}" MAGENTO_URL: "{{ lookup('tls', application_id, 'url.base') }}" +MAGENTO_PHP_HOST: "php" +MAGENTO_PHP_PORT: "{{ lookup('config', application_id, 'services.php.ports.internal.fpm') }}" +MAGENTO_NGINX_PORT: "{{ lookup('config', application_id, 'services.magento.ports.internal.http') }}" + +MAGENTO_NGINX_CONF_PATH: "{{ [ lookup('container', application_id, 'directories.volumes'), 'nginx-default.conf' ] | path_join }}" +MAGENTO_PHP_FPM_CONF_PATH: "{{ [ lookup('container', application_id, 'directories.volumes'), 'php-fpm.conf' ] | path_join }}" + MAGENTO_SEARCH_CONTAINER: "{{ lookup('config', application_id, 'services.search.name') }}" MAGENTO_PHP_CONTAINER: "{{ lookup('config', application_id, 'services.php.name') }}"