A collection of Dockerfiles specifically curated for development purposes.
The base images in these Dockerfiles are parameterized, allowing them to be built on top of any compatible distro images such as Debian, Alpine, and others.
The overarching concept is to facilitate the creation of images that can be stacked atop each other, depending on specific requirements. For instance, one could build an image premised on the latest Ubuntu version, customized for developing in Golang and Python. This process allows for greater flexibility and customization in the development environment.
docker build golang/debian/ -t my_dev_container --build-arg BASE_IMAGE=ubuntu:latest
# Then use the newly built image as the base image of the next one.
docker build python/debian/ -t my_dev_container --build-arg BASE_IMAGE=my_dev_container
These images are designed for local development and testing. They incorporate a user within the container that utilizes a high-range id of 61000. This is done to avoid any potential overlap with host user IDs.
The following command builds a shell image on top of the Ubuntu image. It also set the username of the container user as dev
.
docker build shell/debian -t your_registry/ubuntu-shell \
--build-arg BASE_IMAGE=ubuntu:latest \
--build-arg USER=dev
Or it could be built on top of any other compatible(same distro, e.g. debian) image.
docker build shell/debian -t your_registry/shell \
--build-arg BASE_IMAGE=your_registry/dev:latest \
--build-arg USER=dev
It's crucial to note that the shell and the base image should be compatible. For instance, the Dockerfile located at
shell/debian/Dockerfile
is compatible with any debian-like distributions. Therefore, it can be readily used on top of other systems such as Ubuntu, Debian, and so forth. This compatibility allows for seamless integration and efficient usage of resources, enhancing your overall development experience.
Using Linux docker engine where development within a container is preferred and the host's source code directory needs to be mounted as a volume, write permission must be granted to the container's user id. This permission management is usually handled by Docker Desktop in MacOS and Windows. However, when working with a Docker engine in an operating system such as Linux, this task falls upon the user.
A solution to this is to create a container user that shares the same USER_ID
and GROUP_ID
as the host's current user. This can effectively manage permissions and ensure seamless operation.
Building a shell using the host's current user and group IDs:
docker build shell/debian -t pv/ubuntu-shell \
--build-arg BASE_IMAGE=ubuntu:latest \
--build-arg USER_ID=$(id -u ${USER}) \
--build-arg GROUP_ID=$(id -g ${USER})
The build.sh
script is a versatile tool that you can use to craft images either on your local machine or within a cicd pipeline. Here are some examples to get you started, along with a few predefined images and their layers which can be quickly implemented.
To clean up the environment, the easiest way is to prune the system which deletes all the images, containers, and networks that are not being used.
docker system prune -a
Then, pull the ubuntu image to start fresh.
docker pull ubuntu:latest
Beyond its basic functionalities, build.sh
offers the capability to create two additional shell images on top of the pv/dev
image. By using the -s
option, you can add a shell layer to pv/dev
that incorporates a container's user. Meanwhile, the -c
option allows you to construct a shell layer with a container's user that shares the same USER_ID
and GROUP_ID
as the host's current user.
Note: The order of the layers is crucial. Layers that depend on others should be arranged accordingly. For instance, the sequence could be ...golang,hugo,... .
To illustrate, let's create an image named my-image
based on ubuntu:latest
. This image will then have a series of layers built on top of it. Finally, a shell image with a user named dev
will be constructed. Here's how you can achieve this:
./build.sh -d debian -b ubuntu:latest \
-i python,nodejs,golang,awscli,aws_cdk,shell \
-t my-registry/my-image:latest \
-u dev
On a Linux docker engine, use the
-c | --current-user
option to generate a container user with the same USER ID as the host's user. The-c
option adopts the current user's USER_ID and GROUP_ID, while-s
applies 61000 (a high range ID) for both USER_ID and GROUP_ID. MacOS and Windows users need not worry about this as Docker Desktop manages access to the host's file system.
Recipes are defined in build.sh
. They are a collection of layers that can be built on top of each other. For all recipes by default, the user is set to dev
and the base image is ubuntu:latest
.
If a recipe is dependent on another recipe (as the base image), then the base recipe should be built first. For example, the anaconda
recipe is dependent on the anaconda-base
recipe. Therefore, the anaconda-base
recipe should be built first.
docker pull ubuntu:latest \
&& ./build.sh -r ubuntu \
&& ./build.sh -r anaconda-base \
&& ./build.sh -r anaconda \
&& ./build.sh -r development-base \
&& ./build.sh -r development \
&& ./build.sh -r hugo
Note: In above, pulling the ubuntu:latest
image at first is optional, as .build.sh
script will pull it if it's not already available.
If you're targeting multiple platforms for your image, you'll need to use the --platform
option along with a list of the desired platforms. Here's how you can do this:
./build.sh -r development \
--platform linux/amd64,linux/arm64 \
--registry $DOCKERHUB_USERNAME \
--push
The command builds the development
recipe for both amd64
and arm64
architectures. Once the image is built, it will be pushed to the Docker Hub repository.
Building anaconda
recipe using --no-cache
to force a rebuild of the image without using the docker's build cache.
./build.sh -r anaconda --no-cache