Solving Docker’s ‘wait for database’ problem

22 Nov 2019

Cloud  Docker 

Originally posted at https://tech.labs.oliverwyman.com/blog/2019/11/22/docker-wait-for-database/

One of the problems very familar to anyone doing anything with Docker is that of knowing when a service has become available. If you’re working with Kubernetes, then there are various probes that solve the problem there, but for some reason this has never been solved fully in Docker as such. They are aware it’s a problem, and for many cases you can use the excellent wait-for-it script, but for some situations this isn’t enough. Databases are the canonical case for this since the fact that they are responding to a port doesn’t mean they’re "up" (SQL Server for example has a notable gap between listening to a port and being able to connect properly), and even if they’re up, that doesn’t mean you should start running your test suite yet as there may still be migrations to run or other initialisation scripts.

There are some items out there for doing some of this (e.g. for Postgres) but there has always been lacking a more general tool. I’ve therefore created wait-for-db, which has both built-in Postgres support, and support for any database you have ODBC drivers for, which is at least most of the SQL ones. Most importantly, it’s distributed as a static binary with zero dependencies, but still only comes in at 2.2 Mb which I’d call small enough. This works for x86 Linux, which is what most Docker images are (patches welcome for other systems), and there shouldn’t be too many issues building it for other systems (there’s a OS X binary as well, but not really useful for Docker images).

It works by literally doing what your app would do: connect to the database and try to run a SQL command. If you pick a suitable command (e.g. one that only returns results when your migrations are complete) then it should do all the right things for you. Most importantly it will keep retrying the connection/script until it works (with an optional timeout if you want it to fail eventually), with some detection checks for permanent failures e.g. syntax errors, so if a table isn’t available yet it will keep trying, but if your SQL is syntactically wrong it will stop and fail the moment it spots that.

Building a fully static binary for Rust is fiddly sometimes. If you’ve got a pure Rust application, then it’ll statically link to everything except the system libc. Except, this isn’t a pure Rust application, because I’m using odbc-sys which links to the libodbc library. So we have a few things to fix. The libc issue is fixable by compiling for musl on Alpine as per the Rust guide (once you remove proc macros as they don’t work on musl as they need dynamic linking currently), but in order to get this fully working I had to fix a few things in other projects as static linking isn’t one of those things most people do, so it’s not entirely well supported.

That’s quite a few things. In general, static binaries are not really needed in most use cases these days, except when you need something really portable like this, so you do run into some issues when doing this. Thankfully open source projects mean you can fix such matters simply, even when the upstream maintainers are a little slow (looking at you Alpine!).

Previously: Hacktoberfest 2019 Next: How to get a 10x developer