Porting site from Azure Windows+IIS to Azure Linux+Docker

Silverstripe Version: siverstripe/cms: 4.13.10

Question:

Hello, I know next-to-nothing about Silverstripe but as per the title, am trying to move an existing site to Azure Linux + Docker. I’ve gotten so far – the site is working except for the assets (.pngs and the like) but I need help to get those assets displayed.

I started by copying the database and deploying our site repo. to Azure but PHP 8 detected lots of duplicate class definitions so I pruned them out of various directories under vendor/ (mainly including _legacy in the name) until the HTML, CSS and JS was all served. By the way, the composer.json was arrived-at by some trial-and-error, since many combinations of package versions led to a fatal (uncatchable) PHP parse error. This is fixed in nikic/php-parser 5 apparently, but AFAICT no version of Silverstripe works with that.

The Windows site has a huge assets/ hierarchy but when I verbatim copy that to the Linux site (via tar), it just 404ed on the home page. Nothing in app/.error_log or anywhere else. With an empty assets/ directory, the site works but does not serve the assets, obviously. This is where I am stuck now. I presume the assets/ hierarchy is auto-generated somehow but just copying it, and the database, from one installation to another is not enough to make it work. What have I missed?

** composer.json **
{
	"name": "silverstripe/installer",
	"type": "silverstripe-recipe",
	"description": "The SilverStripe Framework Installer",
	"require": {
		"php": ">=7.3.0",
		"silverstripe/recipe-plugin": "^1.2",
		"silverstripe/recipe-cms": "*",
		"silverstripe-themes/simple": "~3.2.0",
		"vulcandigital/silverstripe-hashupload": "^1.0",
		"ryanpotter/silverstripe-cms-theme": "^3.4",
		"vulcandigital/silverstripe-sendgrid": "^1.3",
		"nikic/php-parser": "^4",
		"jonom/focuspoint": "^3.0",
		"silverstripe/googlesitemaps": "^2.1"
	},
	"require-dev": {
		"phpunit/phpunit": "^5.7"
	},
	"extra": {
		"project-files-installed": [
			"app/.htaccess",
			"app/_config.php",
			"app/_config/mysite.yml",
			"app/src/Page.php",
			"app/src/PageController.php"
		],
		"public-files-installed": [
			".htaccess",
			"index.php",
			"install-frameworkmissing.html",
			"install.php",
			"web.config"
		]
	},
	"config": {
		"process-timeout": 600
	},
	"prefer-stable": true,
	"minimum-stability": "dev"
}

** .env **
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_USERNAME="<<omitted>>"
SS_DATABASE_PASSWORD="<<omitted>>"
SS_ERROR_LOG="app/.error_log"
SS_DATABASE_SERVER="<<omitted>>"
SS_DATABASE_PORT="3306"
SS_DATABASE_NAME="<<omitted>>"
SS_ENVIRONMENT_TYPE="dev"

AFAIK, Silverstripe requires PHP <= 8.1, so check you don’t have more recent versions.

You are not supposed to touch anything under vendor/, so do a rm -rf vendor/* && composer update to restore that folder. If composer fails, solve the issue in composer.json and rerun composer update.

Silverstripe CMS 4.x can be installed with PHP 8.1+, but there will be deprecation warnings.

The Windows site has a huge assets/ hierarchy but when I verbatim copy that to the Linux site (via tar), it just 404ed on the home page. Nothing in app/.error_log or anywhere else. With an empty assets/ directory, the site works but does not serve the assets, obviously.

As for the assets directory, by default Silverstripe CMS wants that to be in public/assets - see the 4.1.0 changelog about how to set that up if you’re using a legacy setup with assets in the root directory of your project.

It doesn’t sound like that’s the problem in this specific scenario, but it’s something to rule out.

I downgraded the instance to PHP 8.1 but it made no difference.

Regarding manual changes to vendor/ – I know. But how can you make it work otherwise? With the composer.json given below for example, the server logs:

NOTICE: PHP message: PHP Fatal error:  Uncaught Exception: There are two files containing the "SilverStripe\AssetAdmin\GraphQL\CreateFileMutationCreator" class: "/home/site/wwwroot/vendor/silverstripe/asset-admin/_legacy/GraphQL/CreateFileMutationCreator.php" and "/home/site/wwwroot/vendor/silverstripe/asset-admin/code/GraphQL/CreateFileMutationCreator.php" in /home/site/wwwroot/vendor/silverstripe/framework/src/Core/Manifest/ClassManifest.php:639

and there are more than 100 duplicate class definitions in vendor/, so whatever the orthodoxy about leaving composer to manage the dependencies, something is wrong in the set of packages that composer pulls-in in this case.

Regarding Guy’s proposal to move assets into public/assets, it seems like a lot of work for something that the SS manual says is entirely optional. Surely if there were any problems caused by the non-current layout, there would be logs about it – but there are none.

It would be useful to know how the assets hierarchy was created but we no longer have access to the original developer or the system s/he used to create the hierarchy.

One other point to emphasise is that this deployment is Docker + nginx; there’s a lot of references to .htaccess in the SS documentation, and any such files will of course be ignored by nginx.

{
	"name": "silverstripe/installer",
	"type": "silverstripe-recipe",
	"description": "The SilverStripe Framework Installer",
	"require": {
		"php": "=8.1.0",
		"silverstripe/recipe-plugin": "*",
		"silverstripe/recipe-cms": "*",
		"silverstripe-themes/simple": "*",
		"vulcandigital/silverstripe-hashupload": "*",
		"ryanpotter/silverstripe-cms-theme": "*",
		"vulcandigital/silverstripe-sendgrid": "*",
		"nikic/php-parser": "^4",
		"jonom/focuspoint": "*",
		"silverstripe/googlesitemaps": "*"
	},
	"require-dev": {
        "roave/security-advisories": "dev-latest",
        "phpunit/phpunit": "*"
	},
	"extra": {
		"project-files-installed": [
			"app/.htaccess",
			"app/_config.php",
			"app/_config/mysite.yml",
			"app/src/Page.php",
			"app/src/PageController.php"
		],
		"public-files-installed": [
			".htaccess",
			"index.php",
			"install-frameworkmissing.html",
			"install.php",
			"web.config"
		]
	},
	"config": {
		"process-timeout": 600
	},
	"prefer-stable": true,
	"minimum-stability": "dev"
}

I use NGINX exclusively, so that should not be a problem.

From the error message you get, I still think your vendor folder is corrupt and you should regenerate it. This is what I get here:

$ composer create-project silverstripe/installer ./test ^4.13
...
$ cd test
$ grep -r CreateFileMutationCreator .
vendor/silverstripe/asset-admin/_config/graphql-legacy.yml:        createFile: 'SilverStripe\AssetAdmin\GraphQL\CreateFileMutationCreator'
vendor/silverstripe/asset-admin/_legacy/GraphQL/CreateFileMutationCreator.php:class CreateFileMutationCreator extends MutationCreator implements OperationResolver

As you can see, there is no vendor/silverstripe/asset-admin/code/GraphQL/CreateFileMutationCreator.php file.

Some success: I completely cleared the production/deployment instance in Azure and re-deployed and this time it worked. I don’t know how. Likely it was stuff in composer.lock. So thanks Nicola and Guy for your suggestions so far.
The bit that doesn’t work is navigation away from the home page, or put another way, any route other than / leads to a 404. How is routing done in SS? E.g., one route is /contact-us/ and I searched the entire hierarchies (in both legacy Windows and new Linux/Docker) for it but did not find any useful references so I presume it’s in a database table, but could not find a likely one.
This page says that routing should be set up in .yml files but my search indicates not.

If you want clean URLs you must configure NGINX properly, it does not work out of the box. At the very minimum, you must pass every miss through index.php:

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

For more info, check my modular configuration suggestions or any other tutorial online (there are tons of them).

Thank you Nikola. Azure doesn’t provide out-of-the-box control of nginx, but I found a way to give it what it wanted and the site is working now, as far as I can tell.

I thought it was fixed but it’s not.
All the routes with one component such as /contact-us work, but if I fill-in the details on that page and click the Submit button, it goes to page /contact-us/Form/ which is a 404.
That /contact-us/Form works on the original Windows+IIS site.
Is this something to do with the nginx config. still?
The obvious difference is that /contact-us/Form has two name-components so it seems that routes with one name-component work, but those with more than one do not.

server {
        include mime.types;
        default_type  application/octet-stream;
        listen 8080;
        listen [::]:8080;
        root /home/site/wwwroot;
        index index.php index.html index.htm;
        server_name _; 

        location / {
                #index index.php index.html index.htm hostingstart.html;
                try_files $uri /index.php?$args;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
                root /html/;
        }

        # Disable .git directory
        location ~ /\.git {
                deny all;
                access_log off;
                log_not_found off;
        }

        # Add locations of phpmyadmin here.
        location ~* [^/]\.php(/|$) {
                fastcgi_split_path_info ^(.+?\.[Pp][Hh][Pp])(|/.*)$;
                fastcgi_pass 127.0.0.1:9000;
                include fastcgi_params;
                fastcgi_param HTTP_PROXY "";
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
                fastcgi_param QUERY_STRING $query_string;
                fastcgi_intercept_errors on;
                fastcgi_connect_timeout          300; 
                fastcgi_send_timeout            3600; 
                fastcgi_read_timeout            3600;
                fastcgi_buffer_size 128k;
                fastcgi_buffers 4 256k;
                fastcgi_busy_buffers_size 256k;
                fastcgi_temp_file_write_size 256k;

                fastcgi_keep_conn on;
                fastcgi_index  index.php;
        }
}

This is a completely different issue. My best guess is you have a 500 error (maybe you send an email on form submission and you are missing a PHP extension) and there is no /html//50x.html file.

If this is the case, checking your NGINX logs should highlight the issue.

The site sends a POST to /contact-us/Form/ which 404s.

With the package

"vulcandigital/silverstripe-sendgrid": "*",

installed, shouldn’t that take care of email?

I ask because there is no PHP email extension provided with the Azure implementation. I wonder if that vulcandigital/silverstripe-sendgrid package provides a substitute. We do have a Sendgrid account.

Having asked that, the site does work on my dev. PC with php -S 127.0.0.1:8083.

The phpinfo() output on production gives a sendmail path (/usr/sbin/sendmail) that in fact does not exist, whereas the path on my dev. PC (NixOS) does exist. Since production is an Azure Docker container, I will have to wrestle with Microsoft somehow if it’s necessary to have sendmail available and there are no workarounds.

No, it doesn’t: you cannot change the configuration from PHP code. What’s wrong in checking the webserver logs?

This is the only relevant line from the logs:

2023/12/18 03:27:20 [error] 20122#20122: *2321 "/contact-us/Form" is not found (2: No such file or directory), client: 169.254.137.1, server: _, request: "POST /contact-us/Form/ HTTP/1.1", host: "www.ngv.com.au", referrer: "https://www.ngv.com.au/contact-us/"

It seems that Microsoft deprecates sending email from an Azure container (which is reasonable) so I have to rework the code to use our Sendgrid account instead.


However, no, that can’t be it, because the Azure instance does send the email; it arrives. But with a 404 in the browser. So I don’t even really know what causes the problem.

Well, that was just a guess.

I just checked the HTML code of your page and the action of that form is /contact-us/Form/
If you don’t define that Form route in any YAML or in your $url_handlers (see the routing documentation for details) a “404 Not Found” is thrown. In other words: it is doing what it is supposed to do.

Just for the record, I fixed it by using our own Sendgrid account.

Thanks to Nikita and others for their patient assistance.

1 Like

I read what you write … but: if I ‘run’ the site on my dev. PC with php -S localhost:8080, it works, completely. Email is delivered, no 404. php output is

[Wed Jan 10 10:21:31 2024] 127.0.0.1:52126 [302]: POST /contact-us/Form/
[Wed Jan 10 10:21:31 2024] 127.0.0.1:52126 Closing
[Wed Jan 10 10:21:31 2024] 127.0.0.1:52140 Accepted
[Wed Jan 10 10:21:39 2024] 127.0.0.1:52140 [200]: GET /contact-us/thank-you/
[Wed Jan 10 10:21:39 2024] 127.0.0.1:52140 Closing
[Wed Jan 10 10:21:39 2024] 127.0.0.1:53618 Accepted
followed by a load of GET assets/resources, and the browser finishes on /contact-us/thank-you.

So whatever is needed by SS to make the form work, is there, but for some reason it doesn’t work in Azure. I suspect this is related to MS not wanting us to send email from containers but to use an SMTP relay service. Not a SilverStripe problem at all.