2019 in review: goodbye Windows, hello ddev
Two things I had been putting off for years both happened in 2019, and both were about the machine I work on rather than the code I write. I moved off Windows to Ubuntu for good, and in the middle of December I put a ddev config in a project for the first time.
Five years earlier I wrote a post on this blog recommending a Windows GUI to compile your Less files. That is how far this year moved me.
Leaving Windows
I had been dual-booting and pretending that counted. What finally decided it was the gap between my machine and the servers I deploy to. Every one of those servers is Linux. Every path, every permission, every case-sensitive filename, every line ending was a small translation I was doing in my head all day, and getting wrong often enough to waste afternoons.
The things that actually improved, in the order I noticed them:
- Case sensitivity. A template referenced as
Header.twigwhile the file isheader.twigworks on Windows and 404s on the server. That class of bug simply stopped existing. - Permissions are real.
chmod 755means something locally now, so the permissions I set are the permissions I test. - The terminal is the same terminal. Every command in a deploy script, every
find, everygrep, behaves identically to how it behaves over SSH. - Speed. Not a headline feature, but PHP and Composer on Linux are noticeably quicker.
What I gave up: one design tool I still occasionally miss, and about a week of productivity while I rebuilt muscle memory.
ddev, and why it stuck immediately
Before December my machine had one PHP version, one MySQL, one nginx, and every project had to agree with them. They do not agree. One long-running platform is on an old PHP, a new build wants 7.2, and the day one of them upgrades I get to reinstall everything and rediscover which extension was missing.
ddev fixes that by putting the environment in the repository. This is close to the first config I committed:
name: example
type: php
docroot: ""
php_version: "7.2"
webserver_type: nginx-fpm
mariadb_version: "10.1"
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
nfs_mount_enabled: true
timezone: Europe/Amsterdam
The commit message I wrote at the time says what mattered, better than I could summarise it now: a config which mimics the specs of the production server.
That is the whole point. Not "a container", but this PHP version, this database version, this web server, this timezone, matching the machine the code will actually run on. Every "works on my machine" bug I had been chasing for years was a difference between two specs that nobody had ever written down.
Three practical notes from the first two weeks:
nfs_mount_enabled: true. Without it the filesystem sync is the bottleneck and a framework
request that should take 80ms takes 800ms. Turn it on, do the one-time host setup, and it feels
native.
Set the timezone in the config. Otherwise the container is UTC, your production server is not, and you get to debug a date bug that only appears between midnight and 02:00.
ddev import-db and ddev ssh are the two commands you actually live in. Pull down a copy,
anonymise it with the command from last year, work. And ddev ssh puts you inside the same
environment the code runs in, so the PHP you test with is the PHP that serves the request.
What I did not anticipate: it changed how I hand work over. "Clone this and run ddev start" is a
very different conversation from a page of setup instructions that were accurate eight months ago.
Xdebug, inside the container
One knock-on effect worth writing down. I had been using PhpStorm since 2015, and moving into containers briefly broke the part of it I rely on most: the debugger no longer had anything to attach to.
The fix is a config flag and a port mapping, and it is worth the twenty minutes. Without it a container is a black box you run. With it you can stop the code mid-request and look inside, which is the difference between using the environment and trusting it.
Less to Sass
Not because Sass is dramatically better. Because everything else had already moved. Every starter,
every framework's default build, every answer online assumed .scss. Staying on Less meant
translating other people's code before I could use it.
The conversion is mostly mechanical:
// Less
@brand: #c2410c;
.rounded(@r: 3px) { border-radius: @r; }
.button { .rounded(4px); background: @brand; }
// Sass
$brand: #c2410c;
@mixin rounded($r: 3px) { border-radius: $r; }
.button { @include rounded(4px); background: $brand; }
Sigils change, mixins become explicit with @include, guards become @if. Half a day per project,
and I diffed the compiled output to be sure nothing moved.
Queues on hosting that will not let you have a supervisor
The other useful thing this year. Most Laravel queue documentation assumes you can install Supervisor. On a lot of shared hosting you get SSH, a cron table, and a watchdog that kills long-running processes.
You can still run queues there, in short bursts:
* * * * * cd /home/user/domains/example.com && /usr/local/bin/php artisan queue:work --stop-when-empty --max-time=50 --tries=3 >> storage/logs/queue.log 2>&1
--stop-when-empty: exit once the queue is drained instead of idling. An idle PHP process on shared hosting is a process that gets killed and logged as abuse.--max-time=50: stop before the next minute's cron fires, so workers never stack.--tries=3: three attempts, then the job lands infailed_jobsinstead of looping forever.
Latency is up to sixty seconds. For sending mail or generating a PDF, nobody notices.
The part people skip: with retries, a job that half-finished and then threw runs again from the top. Guard it.
public function handle()
{
if ($this->invoice->fresh()->sent_at !== null) {
return;
}
Mail::to($this->invoice->customer)->send(new InvoiceMail($this->invoice));
$this->invoice->forceFill(['sent_at' => now()])->save();
}
And add a second cron that mails you the contents of failed_jobs, because otherwise that table
fills up in silence and you hear about it from the client.
In short
Moved to Ubuntu, put ddev in a project for the first time, converted Less to Sass, and got queues running on hosting without a supervisor.