Search code examples
phpapachedockerslim

Docker + Apache + PHP-FPM static content


I am in the process of moving over some sites in PHP using Slim/Twig to docker containers. In the old server a request came in slim routed the request sent back the html and then the browser made the call to get the resources CSS, images, etc. where apache took over.

Moving to Docker the httpd conf is essentially this:

ProxyPassMatch "^/(.*\.php\/(.*)?)$" "fcgi://php:9000/var/www/html/public/index.php/$2"

Still working all that out but this essentially forwards a request like http://192.168.33.20:8080/index.php/admin to fcgi://php:9000/var/www/html/public/index.php/$2 and Slim picks off all the rest of the path so admin and returns the correct view rendered by twig.

Issue I am running into is the resources. I have most of my css and front-end frameworks in the php application via composer. So the browser makes another call to:

/index.php/vendor/twbs/bootstrap/dist/js/bootstrap.bundle.js HTTP/1.1" 404

Getting a 404 because Slim has no idea what this path is and apache is just forwarding.

What I have looked at and the cons:

  1. Placing the resources on Apache but this basically couple Apache and php like the server was.

  2. Creating a container to server these files ie another non-loadbalancer Apache to server these requests. This still couples and also means I need to find a way to potentially store multiple versions in a blue green deployment.

  3. Mount a volume to the Apache lb. Not sure why I abandoned this one. Probably version issues.

  4. Create a route that can search for the resource. So far my favorite idea but added code complexity.

So the question I have is there a standard way of dealing with this in Docker?


Solution

  • My standard practice is to control resource access by framework route. An example implementation (in Lumen):

    $router->get('/asset[/{path:.*}]', 'AssetController@load');
    

    An example request and response looks like this:

    GET /asset/js/app.js HTTP/1.1
    Host: localhost:8080
    Connection: keep-alive
    Pragma: no-cache
    Cache-Control: no-cache
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
    Accept: */*
    Referer: http://localhost:8080/spa/example
    Accept-Encoding: gzip, deflate, br
    Accept-Language: en-US,en;q=0.9
    
    HTTP/1.1 200 OK
    Server: nginx
    Content-Type: application/javascript
    Content-Length: 21056410
    Connection: keep-alive
    Last-Modified: Sun, 31 Jul 63 19:34:21 +0000
    Cache-Control: private, must-revalidate
    Date: Wed, 31 Jul 2019 19:34:21 GMT
    Accept-Ranges: bytes
    X-XSS-Protection: 1; mode=block
    X-Content-Type-Options: nosniff
    Content-Security-Policy: default-src  'self'; connect-src  'self'; img-src  'self'; style-src 'unsafe-inline' fonts.googleapis.com 'self'; font-src fonts.gstatic.com 'self'; script-src 'unsafe-inline'  'self' 'unsafe-eval'
    

    Your controller (e.g., AssetController in my earlier example), would take care to translate the URL path to the file system path (watch out for .. and other tricks, use realpath and compare it to your code's installation base path), set the Content-Type and Content-Length headers, any relevant caching headers, and then stream the file.

    While that does add complexity and a certain amount of overhead, it provides the flexibility to constrain resources by business logic.

    If performance is a concern, you can promote the access to the web server or shield the route behind a CDN and proxy content.