FLMP: A Modern PHP Dev & Deployment Stack
In recent years, our dev team had grown increasingly frustrated with the limitations of traditional PHP dev environments like WAMP, MAMP, and XAMPP. We needed something faster, more consistent, and production-like. That’s why we created and open-sourced FLMP: a modern, containerized stack for PHP development and deployment.
Traditional dev stack limitations
For decades, PHP developers have relied on stacks like WAMP, MAMP, XAMPP, LAMP, and LNMP for local development. While these solutions were once a conventient standard, they now present several significant limitations:
- Performance bottlenecks: Apache’s process model and older PHP-FPM setups can be slow, especially under load or with modern PHP applications. Nginx improves some aspects, but still relies on external PHP processes.
- Configuration complexity: Setting up HTTPS, HTTP/2, and modern security features is often cumbersome and error-prone. Each stack has its own quirks, and replicating production-like environments locally is rarely straightforward.
- Inconsistent environments: Differences between local, staging, and production setups (OS, PHP versions, extensions, config) can lead to “works on my machine” bugs and deployment surprises.
- Manual dependency management: Installing and updating PHP, MySQL, and web servers natively on your OS can cause version conflicts and break other projects.
- Limited support for modern workflows: These stacks were not designed for containerization, CI/CD, or integration with modern frontend tooling. Integrating with tools like Docker, Node.js, or SPA frameworks is often awkward.
- SSL and CORS headaches: Modern browsers require HTTPS for many features (cookies, service workers, etc.), but setting up trusted SSL locally is a pain. CORS issues are common when running separate frontend and backend servers.
- Resource heavy: Running full VMs or heavyweight local servers can consume significant system resources, slowing down your machine and other apps.
- DNS limitations: Most local dev stacks rely on
localhostor custom hostnames, but configuring DNS or/etc/hostsentries can be inconsistent, especially when working with multiple projects or on different networks. - OS consistency issues: WAMP, MAMP, and XAMPP are platform-specific, and subtle differences between Windows, macOS, and Linux can cause unexpected bugs or require extra configuration to achieve parity across team members.
These pain points led us to seek a better, faster, and more reliable solution—one that would make local development as close to production as possible, while also being easy to set up and maintain.
Why Move Beyond LAMP?
The classic LAMP stack (Linux, Apache, MySQL, PHP) has powered PHP application for decades. But it’s never been ideal. Apache’s name is revealing, it comes from “A patchy webserver” - i.e. it has always been somewhat makeshift, complicated to configure and non-performant. In recent times Nginx has often been implemented as an alternative, or in addition, as a reverse Proxy or load balancer to help with speed issues. However, that’s really just another patch job and we felt it was time for something more modern.
An important part of the stack’s core architecture is the server process management layer - i.e. the code that manages the interface between the webserver and the PHP language. For the past decade or more the standard has been PHP-FPM. FPM stands for FastCGI Process Manager. With our long experience of PHP (since v3) we remembered a time when PHP-FPM was the new kid on the block. Prior to that the standard it was slower the Common Gateway Interface (CGI), where a new PHP process was started and killed for every single request. FastCGI came into prevalence around 2004, during the PHP v5 era, as it was baked into the language itself as a php-cgi binary. However, PHP-FPM was only bundled with PHP in 2010, and since then has pretty much been the standard.
When that change from CGI to PHP-FPM was made, the performance gains were very noticable and so with that history in mind it seem prudent to see if process management tech hadn’t evolved in the interim.
We also explored a few other routes for performance gains. Another low-hanging fruit was the webserver, Apache. We had been experimenting with other webservers including Nginx but the one that stood out was Caddy because of it’s combination of performance, simplicity to configure, and community and commercially available support.
Alternatives Considered
Native Caddy
Caddy is a great webserver with automatic HTTPS, but integrating it with PHP (via FastCGI) still left us with some of the old pain points: process management, debugging, and less tight PHP integration. We did create a prototype of this which you can see at https://github.com/arc2digital/CLMP and for a while this became our new development standard because it already had the other benefits (discussed in more detail below) of using docker.
Phalcon
Phalcon is a high-performance PHP framework written as a C extension. While it may be even faster, it comes with big trade-offs:
- Framework lock-in: You’re tied to Phalcon’s MVC conventions
- Debugging challenges: Most of Phalcon is compiled C, so you can’t easily insert debug probes or step through the framework internals
- Ecosystem limitations: Fewer plugins, low adoption and less community support
What is FLMP?
FLMP stands for FrankenPHP, Linux, MariaDB, PHP. At its core is FrankenPHP, a lightning-fast, Go-based PHP application server and reverse proxy, built as a Caddy webserver module. This means:
- Native HTTP/2 and HTTP/3 support
- Automatic HTTPS (even for localhost!)
- Hot reloading and zero-downtime restarts
- Much faster request handling than Apache or Nginx + php-fpm
Architecture Overview
FLMP uses Docker Compose to create a network between two main containers:
- FrankenPHP (Caddy + PHP): Handles all web requests, HTTPS, and PHP execution
- MySQL/MariaDB: Database container
Optionally, you can connect a frontend dev server (e.g., VueJS) on port 8080, with Caddy reverse-proxying requests to it.
Project structure (see the repo):
.
├── db/ # MySQL data
├── frankenphp/ # Caddyfile, Dockerfile
├── app/
│ ├── be/ # Backend PHP app
│ ├── fe/ # Frontend app (optional)
│ └── dist/ # Frontend build output
├── docker-compose-php8.yml
Benefits & Speed Gains
In our experience, FLMP is significantly faster than LAMP, even with Nginx as a proxy. FrankenPHP’s multi-threaded Go core and tight PHP integration mean snappier page loads and a much tighter feedback loop for developers. Plus, Docker ensures your local and production environments are identical.
Why FrankenPHP?
FrankenPHP gives us the best of both worlds: the speed and modern features of a Go-based server, with the flexibility to run any PHP codebase—WordPress, Laravel, custom apps—without framework lock-in. Debugging is straightforward, and you’re never forced into a particular architecture.
Using FLMP Locally
- Clone the repo:
git clone https://github.com/arc2digital/FLMP.git cd FLMP - Add your PHP app:
Place your backend code in the folder defined by the
rootdirective infrankenphp/Caddyfile(e.g.,app/be/). - Start the containers:
docker-compose -f docker-compose-php8.yml up - Trust the local HTTPS cert:
Add the generated root cert (found in
caddy/data/caddy/pki) to your browser’s trusted store. - Access your app:
- Backend:
https://be.dev.localhost - Frontend (if using):
https://fe.dev.localhost(with your dev server running on port 8080)
- Backend:
Note: The domain names must be fully qualified (at least two dots, e.g., be.dev.localhost).
Deploying FLMP to a Server
- Copy your app and config files to your deployment server.
- Install Docker and Docker Compose on the server.
- Start the stack:
docker-compose -f docker-compose-php8.yml up -d - Configure DNS to point your production domains to the server’s IP.
- Caddy/FrankenPHP will automatically handle HTTPS certificates for real domains.
FrankenPHP installation options (and why we chose a custom build)
FrankenPHP offers several official installation paths (see the docs at https://frankenphp.dev):
- Install script: quick install of the FrankenPHP binary for local use.
- Docker image: official container image for cloud-native deployments.
- Standalone embedded binaries: package your app as a single self-executable binary.
- Custom builds (from source / xcaddy): compile with specific build tags or add extra modules and tooling.
We chose a custom Dockerfile in FLMP so we could:
- Build on AlmaLinux — a security-conscious, stable successor to CentOS that we consider a sensible default for PHP server workloads. You can swap this out for your preferred distro, but you’ll need to rewrite the Dockerfile steps accordingly.
- Build PHP 8.3.10 from source with the exact flags we need (including CLI support).
- Install Xdebug for development.
- Include Composer in the image.
- Add Postfix + SPF tooling for email workflows in dev.
- Run both FrankenPHP and Postfix via supervisord.
- Ship common build dependencies and libraries used by real-world PHP apps.
That custom build is also where we compile FrankenPHP from source using Go, which keeps the web server and PHP runtime tightly aligned.
FrankenPHP and the PHP CLI
One of the draw-backs to Frankenphp is that because it runs within a Go process, it doesn’t provide direct access to a CLI for PHP.
In our FLMP solution, the PHP CLI is available. We compile PHP from source with --enable-cli and copy /usr/local/bin/php into the final docker image, so CLI tasks run against the same PHP version as the web runtime. That’s deliberate: it keeps Composer, migrations, and build scripts consistent with production.
If you’re using a stock FrankenPHP installation, there are still multiple ways to run CLI code:
- Use the built-in FrankenPHP CLI entrypoint (e.g.,
frankenphp php-cli script.phpfrom the upstream tooling), but it’s not colloquial and it’s not easy to pass parameters to php that way.. - Run
phpfrom a separate PHP CLI image/container and bind-mount your project. - Use a local PHP installation for CLI tasks.
Troubleshooting & Tips
- Session/cookie issues on localhost? Modern browsers require HTTPS for cookies on localhost. FLMP’s Caddy/FrankenPHP setup solves this by generating and trusting local HTTPS certs.
- Custom domains: Make sure your
/etc/hostsfile maps your dev domains (e.g.,be.dev.localhost) to127.0.0.1. - Hot reload: FrankenPHP supports zero-downtime reloads for config/code changes.
Conclusion
FLMP is a fast, modern, and flexible PHP stack for both local development and production. It’s open source—check it out on GitHub—and we welcome feedback and contributions!