Docker/Containers have become the de facto standard for building and deploying applications. The isolation, portability and easy scaling capabilities of containers make them the popular choice for app deployments.
However, containers are not only for application deployments but can also be for local development. They can solve many developer issues. The use of Docker containers during development can have the following advantages.
- runs on my machine = runs anywhere
- there are no cumbersome configuration/version incompatibilities
- The development environment is closer to production
- easy onboarding of new developers
Let's see how I use Docker for the development of this blog.
Base Image
TheOverEngineeredBlog is built on next.js which needs node. Also, the package manager of choice is yarn. The official node image on docker hub is here. It includes yarn too. This blog uses node:lts
image to get the latest lts version of node.
Docker Compose
I created a docker-compose.yml file at the root of the project to define the entire container configuration and add more containers if necessary later.
docker-compose.yml View on GitHub
version: '3.7'
services:
runner:
image: node:lts
ports:
- "$PORT:3000"
- "$DEBUGPORT:9229"
volumes:
- .:/app:cached
- yarn-cache-volume:/usr/local/share/.cache/yarn/v6:cached
working_dir: /app
command: "$COMMAND"
volumes:
yarn-cache-volume:
The compose file defines a service named runner
using the base image "node:lts".
The ports
section instructs Docker to expose ports 3000 and 9229 at $PORT and $DEBUGPORT on the host. PORT and DEBUGPORT are environment variables to configure the desired ports on the host.
The volumes
section defines mounts and named volume. The root directory of the project is mounted to /app
inside the container. Also, it defines a named persistent volume for yarn cache. Docker manages this volume and persists it through the container stop/start. This cache reduces yarn execution time next time the container starts.
working_dir
set the current directory to ./app
to avoid changing the directory each time the container starts.
command
is set to an environment variable $COMMAND. It can be supplied when invoking docker-compose.
RUN script
I like to have a run
script to spawn the container using docker-compose to avoid writing the same commands each time.
run View on GitHub
#!/bin/sh
export PORT=${PORT:-3000}
export DEBUGPORT=${DEBUGPORT:-9229}
export COMMAND=${@:-"yarn dev"}
EXISTING_CONTAINER_ID=""
if [-n `docker-compose ps -q runner`]; then
EXISTING_CONTAINER_ID=`docker-compose ps -q runner`;
elif [-n `docker ps -q --no-trunc | grep $(docker-compose ps -q runner)`]; then
EXISTING_CONTAINER_ID=`docker ps -q --no-trunc | grep $(docker-compose ps -q runner)`;
fi
if [-z $EXISTING_CONTAINER_ID]; then
COMMAND=${@:-"yarn dev"} docker-compose run --service-ports --rm runner
else
echo "Existing container ${EXISTING_CONTAINER_ID}"
docker exec -it ${EXISTING_CONTAINER_ID} ${COMMAND}
fi
The script is executed like this.
[PORT=<desired port on host> DEBUGPORT=<desired debug port on host>] ./run [<command>]
DEFAULTS PORT=3000 DEBUGPORT=9229 COMMAND="yarn dev"
Sections in []
are optional and have defaults set.
To start the application, I need to write ./run
on the shell. It starts the container, exposing the ports 3000 and 9229 on the host and then runs yarn dev
inside the container.
Any command can be executed inside the container by prefixing it with ./run
E.g. To add a package, run ./run yarn add some-package-name
You could also do ./run bash
to get a bash shell attached to the container. This bash shell can be used to execute commands inside the container without the prefix './run'
The script also checks if a container is already running for the application and reuses the container to execute the command. Credits to this answer on ServerFault
We can also write a similar script for windows machines using cmd/PowerShell.
This setup has helped me enormously. I don't have to worry about installing different versions of node/java/python etc. Besides, now the only dependency for local development is, Docker!
Top comments (0)