td204

Web development, one post per year since 2002

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.twig while the file is header.twig works on Windows and 404s on the server. That class of bug simply stopped existing.
  • Permissions are real. chmod 755 means 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, every grep, 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 in failed_jobs instead 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.

The year in commits

Commits by month in 2019: 1742 in total, peaking at 366 in May.
Jan 2019: 10 commits J Feb 2019: 86 commits F Mar 2019: 78 commits M Apr 2019: 91 commits A May 2019: 366 commits 366 M Jun 2019: 273 commits J Jul 2019: 137 commits J Aug 2019: 122 commits A Sep 2019: 94 commits S Oct 2019: 168 commits O Nov 2019: 149 commits N Dec 2019: 168 commits D