We’ve discussed Docker in past blogs, which can be found here. Let’s go over a brief recap. Docker essentially allows you to run multiple virtual machines in their own separate containers. This allows you to develop, run, and deploy programs uniformly regardless of the operating systems they run on. Whether you're using Linux, Windows, or Mac OS, leveraging Docker allows your team(s) to work without the constraints and headaches of managing or deploying to different operating systems.

The Docker Life Cycle

Containers have a dedicated place throughout your SDLC, though some rules should be established for specific use cases.

Docker in Dev

Within an established team, containers can be an immensely useful tool to ensure platform stability. A mature code base should have some form of `CONTRIBUTING.md` document to help new developers get a local environment up and running. This document might assume pre-built OS platforms:

> All of our developers are issued Microsoft Windows laptops with MySQL Community Server pre-installed.

Or it could require alternate instructions for linux-based systems:

> Install the latest package of mysql-server with apt, yum, or dnf. (Mac users should brew install mysql.)

Even better, there could be instructions on setting up containerized services for development support, regardless of operating system:

> Run latest mysql and redis as docker containers.

The best way, however, would be to include in your code base a compose file defining the services that make up your app:

# docker-compose.yml 

services: 

db: 

    image: mysql 

  cache: 

    image: redis

With this file in place, a developer has only to run `docker compose up` to ensure all services needed by the app are locally installed. Each service defined in the compose file is run as its own container, isolated from any other service which might also be running locally (though port and volume conflicts could easily occur if you’re not explicit in the services definition). Docker handles the necessary steps to ensure service discovery is enabled within the container network, such that service names resolve as hostnames in DNS. If a Dockerfile is already part of your app, you can even include the build instruction in your compose file to complete your app environment.

Docker In Test: CI

Though containerized development environments offer a lot of benefits, the true place that Docker shines is within a CI/CD pipeline. Rather than relying on shell scripts to cobble together build environments and having to maintain multiple databases for each deployment tier, rely on containers to provide these same services on ephemeral resources.

Your specific pipeline strategy will obviously depend on the hosted solution you employ, but the basic steps remain the same regardless of tooling. As an example, let’s consider a CI/CD pipeline for a Python project hosted in GitLab:

default: 

image: python 

  

services: 

  - mysql 

  - redis 

  

test: 

  before_script: 

    - pip install pytest 

  script: 

    - pytest

Here we have declared that our base image is the latest Docker image of Python, with networked service containers for both MySQL and Redis (similar to the dev environment’s compose file). Our CI/CD pipeline consists of a single step identified as `test` which first ensures pytest  has been installed on our default image, then runs the test suite. If any line in the `script` block exits with a non-successful code, our pipeline will fail.

Similar to other build tools, such as Jenkins, CI/CD pipelines can be configured to push build artifacts and containers to registries for use throughout the SDLC. The pipeline could ensure the “latest” tag is always updated when code is merged to the main branch; this container would then be pulled when running `docker compose up` in dev.

build: 

only: main 

  script: 

    - docker build  -t $CI_REGISTRY/app:latest . 

    - docker push $CI_REGISTRY/app:latest

This additional `build` step would, only for the main branch, build and push our docker image to the project’s container registry

Docker in Prod

Just as in Dev, a compose file will allow the project to be run in a production environment; though, security should be much more of a concern. Additionally, production will require some consideration towards resource scalability; but this is precisely the intent of containerization: reproducible, deterministic application and environment configuration.

A development compose file will typically use non-standard ports, volume bind mounts for live-editing of code, and additional extra services such as build and debug tools; all of which will need to be modified for a production environment. This is easily accomplished by extending the local compose file with production-specific values  in a second compose file meant to override and supplement the local file.

Consider a very basic  `docker-compose.yml` file:

web: 

  image: my_python_app 

  depends_on: 

    - db 

    - cache 

  

db: 

  image: mysql 

  

cache: 

image: redis

To extend this stack in a production environment, consider the following `docker-compose.prod.yml` file:

web: 

  ports: 

    - 80:80 

  environment: 

    APP_ENVIRONMENT: prod 

  

cache: 

  environment: 

    TTL: "500"

The CD pipeline would then need to be updated to use:

$  docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

This same strategy could be used with different combinations of compose files for CI, staging, review, and release environments. Infrastructure as Code (IaC) principles then become part of the code base, further strengthening the application with deterministic, environment-specific builds.

The JBS Quick Launch Lab

FREE 1/2 Day Assessment

Quantify what it will take to implement your next big idea!

Our intensive 1/2 day session will deliver tangible timelines, costs, high-level requirements, and recommend architectures that will work best, and all for FREE. Let JBS show you why over 20 years of experience matters.

Get Your FREE Assessment