Upon learning of
asdf plugins for backing services like Postgres, Redis, Elasticsearch and others, my first question was whether and to what extent it could replace Docker for providing those services in local development, where asdf already shines for versioning.
The first obstacle for most users will be that
asdf has no equivalent of
docker compose up for booting and monitoring an entire local environment. (As of January 2022 the
asdf discussions page is recently dark but this was an active topic at the time of its disappearance.) Brainstorming the matter with a colleague, he pointed out we were essentially proposing Foreman. A few moments later he found this:
From the oldie-but-a-goodie department: Foreman is a simple tool that reads a list of command line tasks from a file, starts them, feeds their output to
stdout with some nice formatting, and eventually shuts them down when you exit the process; i.e., exactly what
asdf doesn't provide. As outlined above, there isn't even really any "integration" necessary for
asdf and Foreman to play nicely. They just do their own things correctly.
I've been using the approach above where possible for a couple of weeks now, and it's nice. (Be sure to note the trick in the blog post for running Postgres under Foreman control!) Docker is, among other things, a significant resource commitment for local development. On a Mac, you're spinning up an entire separate Linux instance to run software that MacOS is quite capable of running itself. This might be a good tradeoff; for many the benefit of standardizing developer experience or duplicating an idiosyncratic production environment will outweigh other considerations. But for individual developers looking for lightweight solutions—or to avoid duplicating functionality already provided by the kernel, the shell,
asdf and various language-specific dependency managers—this solution is very much worth a look.
I created the following in
~/bin/app, which will probably get (a little) more clever over time:
#!/usr/bin/env zsh foreman start -f Procfile.local
Procfile.local to prevent conflicts with applications that already have a
Procfile for production. In this case we're booting a Phoenix project using Postgres and Redis:
postgres: mkdir -p log/ && postgres-local redis: redis-server api: mix phx.server
elixir main-otp-24 erlang 24.2 postgres 14.1 redis 6.2.6
Booting the application is nice and simple:
$ app 17:05:22 postgres.1 | started with pid 12345 17:05:22 redis.1 | started with pid 12346 17:05:22 api.1 | started with pid 12347 [...logging continues, ctrl-c to exit...]
Future possibilities, difficulties, etc.
The main pain point I see is running multiple applications simultaneously. Docker virtualizes an entire operating system, so if you have two applications, A and B, which both use the same version of (say) Postgres, they still get entirely separate installations, data directories, port 5432 to bind, and so on. With this approach, you have one version of Postgres.
One answer is to separate out the runtime specifics of the service from the codebase, which will presumably depend on the nature of the service itself, but follow a general shape:
- Environment variables
- Configuration files
- Persistent data storage (your database)
- Temporary data storage (logging, lock files)
If you can specify those uniquely for each instance in
Procfile.local, it's likely feasible to run multiple instances of the same service simultaneously. My own research into that will occur when/if I need to run several unrelated applications simultaneously, which is not usually how I work.
Credit where credit is due
Big h/t to Kassio Borges for having thought of this before any of us over here. That link again: Managing Ruby on Rails development dependencies with