DevOps is all about automating the software development lifecycle. One way to do this is through Docker, a platform that enables developers to automate the deployment, scaling, and management of applications. With Docker, developers can containerize their applications, making them easier to manage and deploy. In this blog post, I'll show you how to create a multi-build Dockerfile and deploy it on AWS ECS Fargate using the CTO.ai platform.

Understanding Multi-Stage Dockerfile

A Docker multi-stage build is a method to minimize the size of the final Docker image by running multiple build stages within a single Dockerfile and copying only the necessary artifacts to the final image. This is particularly useful for applications that need a build environment different from the runtime environment or when you want to exclude certain files and directories (like caches, test files, or documentation) from the final image.

Benefits of Multi-Stage Dockerfile

  • Caching and Build Speeds: By structuring Dockerfiles in multiple stages, Docker can cache the results of each stage separately. This can dramatically speed up build times when you're making changes that affect later stages, as Docker can reuse the cached results of the earlier stages.

  • Organization and Maintainability: Multistage Dockerfiles can make your Dockerfiles more organized and easier to maintain. Each stage in the Dockerfile has a specific role (e.g., compiling, testing, packaging), making it clear what each part of the Dockerfile is doing. This can make the Dockerfile easier to understand and modify.
  • Reuse and Sharing: You can reuse a stage in multiple subsequent stages, reducing duplication in your Dockerfile. You can also use stages as a base for other images, making it easier to share common setup steps between different Dockerfiles.

Creating a Multi-stage Dockerfile

Let’s begin by creating a Dockerfile. In this example, we will use the AWS ECS Fargate workflow. You can see the AWS ECS Fargate workflow on our GitHub open-source repository.

############################
# Stage 1: Base Node Image
############################
FROM registry.cto.ai/official_images/node:2.7.4-12.13.1-buster-slim as base
RUN mkdir -p /usr/local/nvm
ENV NVM_DIR /usr/local/nvm
ENV NODE_VERSION 16.19.1

# Install base dependencies
RUN apt-get update && \
    apt-get install -y \
        python3 \
        python3-pip \
        python3-setuptools \
        groff \
        less \
        unzip \
        wget \
        jq \
    && pip3 install --upgrade pip

############################
# Stage 2: NVM and Node Setup
############################
FROM base as node-setup
RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | bash
RUN . $NVM_DIR/nvm.sh \
    && nvm install $NODE_VERSION \
    && nvm alias default $NODE_VERSION \
    && nvm use default

############################
# Stage 3: AWS CLI and Session Manager Plugin
############################
FROM node-setup as aws-setup
RUN pip3 --no-cache-dir install --upgrade awscli
RUN curl -s "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb"
RUN dpkg -i session-manager-plugin.deb

############################
# Final Stage: Application Setup
############################
FROM aws-setup as final
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH

USER ops
WORKDIR /ops

# Add package.json and install dependencies
ADD --chown=ops:9999 package.json .
RUN npm install --loglevel=error

# Add the rest of the application
ADD --chown=ops:9999 . .

# Start the application
CMD ["node", "your-app-start-file.js"]

Here’s a breakdown on writing a multi-build Dockerfile with AWS ECS and AWS Fargate.

  • FROM registry.cto.ai/official_images/node:2-12.13.1-stretch-slim: This line is pulling the base image for the Docker container. It's using the Node.js version 12.13.1 image on a Debian Stretch (slim version) from the CTO.ai registry.

  • RUN mkdir -p /usr/local/nvm: This command is creating a directory for NVM (Node Version Manager) in the Docker image.

  • ENV NVM_DIR /usr/local/nvm and ENV NODE_VERSION 14.18.3: These lines set environment variables for the NVM directory and the Node.js version to be used.

  • The RUN apt-get update && apt-get install -y … command is updating the package list and installing several packages, including Python3, pip for Python3, MySQL client, groff (for AWS CLI), and less.
  • RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | bash: This command downloads and runs the NVM install script.

  • RUN . $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm alias default $NODE_VERSION && nvm use default: This set of commands sources the NVM script, installs the specified Node.js version, sets it as the default, and then uses it.

  • RUN pip3 --no-cache-dir install --upgrade awscli: This command is upgrading the AWS CLI (Command Line Interface) using pip3.

  • The next two RUN commands download and install the AWS Session Manager Plugin, which enables the AWS CLI to start and end sessions.

  • ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules and ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH: These commands set the NODE_PATH and PATH environment variables to include the paths to the Node.js modules and binaries.

  • WORKDIR /ops: This command sets the working directory inside the Docker container to /ops.

  • ADD --chown=ops:9999 package.json . and ADD --chown=ops:9999 . .: These commands add the package.json file and the rest of the current directory's files to the Docker image and change the owner to the user ops and group 9999.

  • RUN npm install --loglevel=error: This command installs the npm dependencies as specified in the package.json file and only logs errors.

  • RUN apt install -y procps lsof telnet: This command installs additional tools - procps (process monitoring tools), lsof (to list open files), and telnet.

  • EXPOSE 3306: This command tells Docker that the container listens on the specified network ports at runtime. In this case, it's port 3306, which is the default port for MySQL.

A multi-stage Dockerfile allows us to use one stage for building our app and a separate stage for running it. We copy only the built artifacts from the build stage to the run stage, leaving behind all the tools and intermediate files needed for building but not necessary for running the app.


Wrapping Up and Your Next Steps

We've now journeyed through the process of configuring a multi-stage Dockerfile on AWS ECS Fargate with CTO.ai. With this knowledge, you're now equipped to create efficient, secure Docker containers, making your applications more robust and production-ready. You can see a sample Dockerfile example here.

To continue this journey, we invite you to speak with one of our Platform Engineers.