Since years, this is one of the topics that comes back every now and then - and until now it was always a choice out of many ways that all had their good sides but also their drawbacks. Until now, I used several things to develop PHP based projects:
- local on Mac, with MAMP
- local on Mac, within a Vagrant-Box
- local on Mac, with Homebrew-based installations of Nginx, PHP-FPM and MySQL
- on a shared Development-Server
But now I learned about ddev and how it solves many of the issues the above solutions had. I've now tested it for a few days and for now this seems to be a very cool solution:
- based on Docker
- config per project can be versioned along with the code
- brings 99% of the things I need just "out of the box"
What was not out ouf the box, could be fixed. Thanks a lot to Kay Strobach for some insights and tipps! I like to share my setup for anyone that wants to use it.
Install Docker and ddev
As ddev is based on Docker, you definitely need to install Docker to your computer. Just follow the ddev documentation about Docker and install it.
Then install ddev itself. Since I'm running a Mac right now, I chose to install ddev via HomeBrew with the following command:
➜ ~ brew tap drud/ddev && brew install ddev
This will install ddev globally on your computer - once done, you're ready to start to configure ddev for your first project.
Prepare your project
To run a PHP project within the ddev Docker-Containers, you have to configure it once. I've now done that to multiple projects (including this website btw, just to easily preview this blog post). As soon as you've done that more than once, it's a matter of seconds to get up to speed... Very neat!
The steps are easy:
- change to your project's main directory with the shell of your choice (e.g. where the composer.json is located)
- start the ddev config wizard
- answer 3 questions (it basically just asks about the name of the project, the desired directory where the document-root for the webserver config shall point to and the type of the project)
- start ddev
The commands for this are:
ddev config ddev start
For several kinds of applications (Drupal, TYPO3, Wordpress, ...) ddev ships with more specific presets or configurations (don't know in detail, not tested). As of now, the Flow Framework doesn't have such presets in ddev - maybe this will change. Until then, just choose the project-type
Depending on the PHP-Version you need or want - just have a look into the
.ddev/config.yaml file. As of now, new setups are configured to run PHP 7.1 but you can change this in that file.
As soon as you start ddev with
ddev start it takes a moment to initially download the docker containers (this happens only once). As soon as the containers are up, ddev tells you, where to access your application:
➜ rimann-org git:(master) ✗ ddev start Starting rimann-org... Starting ddev-rimann-org-db ... done Starting ddev-rimann-org-dba ... done Starting ddev-rimann-org-web ... done Network ddev_default is external, skipping Creating ddev-router ... done Successfully started rimann-org Project can be reached at http://rimann-org.ddev.local, https://rimann-org.ddev.local, http://127.0.0.1:32774
Starting ddev also starts an own container for the database "server" and it's credentials are pretty simple to remember:
Neos: Flow: persistence: backendOptions: host: 'db' dbname: 'db' user: 'db' password: 'db' doctrine: sqlLogger: 'Neos\Flow\Persistence\Doctrine\Logging\SqlLogger' SwiftMailer: transport: type: 'Swift_SendmailTransport'
Besides the database access credentials, this also instructs your application to
- log SQL Queries to
Data/Logs/Query_Development.log- can be omitted if you don't want to use that
- send mails that are created/sent via the SwiftMailer Package to use the sendmail transport (to make sure sent mails get caught to Mailhog for testing)
Now fire up your Browser and visit https://PROJECTNAME.ddev.local/ and it should work :-)
By default, ddev starts multiple Docker containers. Basically everything that is needed to run the PHP based application including PHP-FPM, Nginx Webserver and a MySQL database. But there are some hidden magic features I like to highlight:
- access PhpMyAdmin via http://PROJECTNAME.ddev.local:8036/ (mind the port 8036 in the URL)
- access Mailhog via http://PROJECTNAME.ddev.local:8025 (Port 8025, it shows something like a Webmail interface, listing all mails your PHP application generate/sent via PHP's
As a super Bonus, if you're a Mac user and have SequelPro installed, just execute
ddev sequelpro and it will magically start SequelPro, including the config to access the MySQL-Database within the container.
Work with the container
As your application now runs in the browser, you can continue to develop by editing the local files in your project directory. Don't forget to version your the
If needed, you can access the container via
ddev ssh and it will bring you directly to your project directory within the container.
Optimize the configuration
As said before, ddev runs well just out of the box. But I ran into minor details that bogged me and that I wanted to optimize to get happy with ddev:
Performance, Part 1
Somehow, the Applications seemed to run slow - and reloading the browser after a code change resulted in waiting for >2s which is well, just is not so funny... Add the file below and restart ddev to see an effect.
version: '3.6' services: web: environment: - FLOW_CONTEXT=Development - FLOW_REWRITEURLS=1 - FLOW_PATH_TEMPORARY_BASE=/tmp/Flow
This basically sets two environment variables to instruct the Flow-Framework to
- run in Development Context (well, this is the default anyway, but you could tweak this to
FLOW_CONTEXT=Development/Ddevif you'd like to use different settings for within ddev (aka a sub-context)
- let Flow know, that it should rewrite the URLs properly (e.g. generated links are /foo/bar and not /index.php/foo/bar). This has nothing to do with performance, but makes the application look nicer in the Browser :-)
- put the temporary stuff into
/tmp/Flow- this results in storing the many little files within the container - instead of your project directory (which is just mounted into the container and thus slower on each read/write operation)
Performance, Part 2
Flow does a walk through all files in the installation to check if they changed since the last run - and if so, it issues a recompile of the application's code. This is nice in Development Context as the Code-Cache doesn't need to be flushed manually to actually see/test the new version of the code. But it seems to be rather slow within a Docker-Container - again because it has to access dozens (ok, thousands?) of files that are mounted into the container.
My solution for now is to instruct Flow to freeze almost all packages. With this, Flow just "ignores" those packages and just re-scans the Packages that I'm actually working on - and that's then just a fraction of the number of files to check on each request compared to before.
Just look at
./flow help to find more details on freezing, re-freezing and un-freezing packages.
./flow package:freeze --package-key all
This freezes all packages. After that, you might need to unfreeze those packages you're actually working on:
./flow package:unfreeze --package-key Vendor.PackageKey
To check what's frozen, you can get this information from the list of packages, just run
To make sure the container runs the most-current version of the code upon starting the ddev containers, we can instruct ddev to execute a rescan of all packages - and warmup all code caches when starting ddev.
hooks: post-start: - exec: ./flow flow:package:rescan - exec: ./flow flow:cache:warmup
After adding the above section to the ddev-config file (and a restart of the containers), the listed commands are automatically executed and result in rescanning all (also freezed) packages and building the code caches.
The above mentioned configuration options solved my initial performance issue. From >2s load time in the browser for a simple login form or a list of <10 entries, it went down to somewhat beween 600ms and 750ms which I think is pretty good and in the range I'd like it to be. (Faster would be nice as usual - but that's well acceptable for me)
Now to the only unsolved and not so satisfying point:
Running the Docker containers drains my MacBook's battery way faster than without Docker (device gets hot, fans making noise almost all the time). I have to admit, it's not a brand-new but rather 3-year old notebook and I've not looked into optimizing Docker for power-saving or the like.
This means: If one of you has a good idea to optimize this situation - let me know :-)
Not mentioned so far, but ddev has a great documentation - have a look there if you need more input or want to customize your setup further.