Developing using Docker Compose without a Build (Hey look Ma, no Dockerfile!)

When I first started learning Docker, all the tutorials mentioned creating a Dockerfile. For most practical intents, a Dockerfile is a build file. So that’s how I learned and that’s what I did. Soon thereafter I started spinning up multiple containers to manage apps, dbs, and other services — and it dawned on me that there might be a singular tool from which I could manage these plural containers.

Lo and behold! Docker Compose already existed for this very purpose (okay, so I was a bit late to the game). So I started getting familiar with DC, got the typical tutorial apps up and running, easy peasy.

But there, haunting me from the back of my mind, was this voice saying not unlike the ghost of Jacob Marley questioning “why do you even need a build file, can’t you just pass parameters via docker compose?”.

Well.

My docker explorations soon dwindled and soon too did the rattling chains settle their inquisition. That is—until I suddenly found myself with a lot more free time on my hands, during which I started reading hackernoon pretty regularly. Despite being familiar with the site, I had never noticed that they give out an annual award for Tech Writer of the Year. So down that rabbit hole I went.

The most recent (2019) winner was Patrick Lee Scott, whose article A Better Way to Develop Node.js with Docker immediately piqued my interest. I could hear the chains jangling again but this time they felt more like reins snapping at my bit.

This article, it turns out, is part of a series – the first part of which I had missed entirely. So I skimmed around and realized that much of his app’s contextual underpinnings would not translate directly. Afterall, all I really needed was to get a simple proof of concept working. From there I could explore architectural decisions and start taping down some libraries.

After some unpacking, here is the process by which I got to where I wanted to be all along. Most of this was documented as I discovered the path forward but in some cases I’ve come back to document particularly deep weeds.

 

  1. Install Docker and Compose [MacOS]
    1. In Docker Desktop, Compose is included; this may vary in other docker-based systems.
  2. Create project structure and cd to the project root directory
    1. App Name
      1. app/
      2. server/
      3. .gitignore
      4. docker-compose.yml
      5. Makefile # we’ll talk about this later. Shh. Quiet.
  3. Install node locally in the container(!!)
    1. I thought this would be avoidable, given that my goal was to run node/npm from within the container.
    2. Turns out it is avoidable.
      1. You’ll be able to run node/npm in interactive mode by a pseduo-tty using tty: true in the desired service [docs 1][docs 2]
        1. In case the above docs references are unclear, docker compose allows most of the docker CLI settings to be implemented in a docker-compose file
      2. You’ll then be able to connect to it simply via docker-compose exec <servicename> /bin/bash so in my case, that’s docker-compose exec dev /bin/bash.
      3. If necessary, you can then exit this session without quitting bash via CTRL-p CTRL-q.
      4. This was my holy grail.
  4. Start writing the damn docker-compose.yml. We’ll likely be amending this later so that we can execute some commands, which will render the tty connection invalid, so we’ll likely have to change that as well. But for now, we need it.
    1. version: '3'
      services:
        dev:
          image: node
          volumes:
            - .:/usr/src/service
          working_dir: /usr/src/service
          tty: true
      
    2. The above code is rendered in HTML*, but the file in question is yaml*a subset of JSON™, so mind your Ps and Qs, use tabs, not spaces
      1. *Inserting tabs via html textareas is obnoxious (to put it mildly), so for the time being, please bear with this formatting inconvenience. I’m considering putting the above in a Github repo to eliminate this sticking point?.
  5. After connecting to the container (see 3.2.2 above), generate a package.json using npm init. Fill out the prompts as usual.
    1. Congratulations™! You’ve just initiated a node project without node on your host system!
  6. Run this sucker with docker-compose up
  7. From this point forward we can develop as usual.

This is a good place to stop. I’ll do more on this soon, like getting a server running using some basic node packages like express, nodemon, etc., writing some dev scripts in package.json, and running commands via docker as mentioned above.

Leave a Reply