Containers are not a new idea in the programing world, but when I first encountered Docker, which essentially containerizes all the things, I have to admit I was pretty overwhelemed. It’s a universe unto itself, and there’s definitely a bit of a learning curve when it comes to getting your bearings with this tool/set of tools (because there’s Docker, and then all the different parts of Docker and how they all work together…)
My first steps using Docker had me feeling like I was writing something similar to the pseudo code I sometimes use to hash out ideas. Wich is kind of cool because that means it felt familiar, inviting, unintimidating even. But pseudo code can also kind of feel abstract if you’re not sure what’s happening under the hood. I quickly realised that I needed a refresher in all things networking to really be able to use Docker to it’s full potential. This is especially true if you’re using Docker on a Mac.
Docker plays most friendly with Linux systems because it was conceived for linux systems first and foremost, and requires a virtual machine (vm) to run on a Mac (which carries with it a tinge of irony, considering one of the main arguments for Docker being so great is that it’s so much lighter and faster than a vm… but annnyways…)
When you install docker-for-mac on your system, just keep in mind that any
ports you expose in your
Dockerfile will have to be mapped to ports outside the vm in order to render anything into the browser. Hold on to that thought, because we’ll come back to it in a minute.
Images are snapshots of programs. For example, if you have a development environment all set up, and you want to turn it into an image, you’d create a Dockerfile just outside your project directory and fill it with everything your project needs to run.
Here is an example of an image I created for a locally hosted Gatsbyjs project:
//Dockerfile #build FROM node:9 WORKDIR /var/www/html COPY entrypoint.sh /entrypoint.sh RUN npm install --global gatsby-cli EXPOSE 8888 ENV CHOKIDAR_USEPOLLING=true CHOKIDAR_INTERVAL=1 #run CMD /entrypoint.sh
entrypoint.sh, and copies it’s contents to a new file (in this case a file of the same name)
entrypoint.shand boots up the Gatsbyjs project)
//entrypoint.sh #! /bin/bash yarn gatsby develop --host 0.0.0.0 --port 8888
container is an instance of an
image that you can modify/config to your personal needs. You generally have one service per container - a simple example would be your web service, your backoffice cms, and your database. Each has their own container and if you stop a container, you kill your instance and any data you were using within it is wiped out.
Above I showed you how to create an
image based on local project files. In my
Dockerfile, I had to specify an entry point in a bash file, which runs the commands to boot up the project from the local folder. Since it’s a working development environment, I want to be able to modify my files and see the changes rendered in the browser.
bind mounts, which are specifically designed to allow your changes to be taken into account by the container (or say, if you needed some data to persist through any future containers you might start/stop, but more on this later.)
Essentially we have three options here:
There are volumes, which can be assigned to a mount point in the container, and are either randomly named, anonymous volumes, which are automatically created by Docker when requested by the Dockerfile instructions, or else Named volumes, which you create manually and assign yourself.
Then there there are bind mounts, which link together a directory on your host (for example my local Gatsby project) to a directory in a container (the instance of the image I created of my local Gatsby project).
I chose to use a bind mount in order to be able to continue working on my project from outside the container, in my code editor and have those changes be taken into account in the container (and thus render out into the browser in real time, because of Gatsby’s built in HMR.)
NOTE: a quick word on “mount”, which can be a bit of a ‘fuzzy’ term for some of us _ ahem _ … so what does it mean?
Mounting something refers to taking some data and making it appear in a directory (which we’ll call a mount point). But we’re not actually copying any data here, there are no files being moved around. Instead, our files kind of exist in two places at once. (Yes I did just make that reference. #2000s).
From the pov of the kernel, the files are the same. Mounting basically means telling the computer, “Hey, see that data there, those bits? Make sense of those and mirror them at the point they’re at there, over here at this other point.”
docker build -t gatsbylocal .
-tis a tag flag that your giving to your image, which is helpful when you’re in the terminal figuring out what’s happening
.means build from the current directory. Your location in the terminal when you run this command is important.
docker run -d --name gatsbystory -p 8888:8888 -v $(pwd)/gatsby:/var/www/html gatsbylocal
-dis a tag that gets things running in the background so you can still work in your terminal
--name-ing the container gatsbystory
-ptag maps the
portdefined on the right to the one on the left, which kind of feels backwards, right? But HOSTPORT:CONTAINERPORT. In this case they’re both the same but IRL you can change the port on the left to whatever you want. The command basically makes whatever is running on port 8888 (the port that u exposed in your initial Docker image, remember) accessible by localhost:8888 in the browser. On a linux machine you wouldn’t need to explicitly tell docker where to look. This is entirely because of how Docker works from within a vm on a Mac.
bind mountto the local project folder (in this case, gatsby) and sends any changes made to that folder to the destination path on the server being run in your docker instance.
When you run this command, you’ll have a working instance, and thanks to the mount you set up, any changes you make while developing will be taken into account directly in the browser. Great!
↳ If you’re getting hit with any errors, feel free to compare and contrast with the original project files.
But if each service goes into a seperate container… how do you make containers work together? How would I hook up my Gatsbyjs project to a backoffice cms, (which requires a database,) and how would I funnel the data into my Gatsby project?
Thankfully, Docker provides a handy tool in the way of docker-compose, to help us link together any containers we want - which I explain in Docker (pt2) 👈.