DEV Community

Cover image for Building smarter Docker Images for your development environments with Pants πŸš€
Siddhant Khare
Siddhant Khare

Posted on

Building smarter Docker Images for your development environments with Pants πŸš€

When you think about Docker, what comes to mind? Is it containerized apps for production? Maybe isolated environments for testing? But what if I told you Docker could also be your secret weapon for developer productivity? 🎩

Using Docker images for development environments ensures consistency, eliminates "it works on my machine" problems, and makes onboarding new developers a breeze. But building and managing these Docker imagesβ€”especially in a monorepoβ€”can get messy fast.

Enter Pants, a build tool designed to manage dependencies, optimize builds, and save you countless hours of waiting for Docker builds to finish. While Pants doesn’t directly create development environments, it does the heavy lifting of creating smart, optimized Docker images that are perfect for tools like Dev Containers, or even plain Docker CLI.

Here’s how I made my Docker workflow smarter, faster, and a whole lot less frustrating with Pants.


Why development environments with Docker are hard

Docker is supposed to simplify development, right? Just run a container, and you’ve got a fully isolated, consistent environment! But reality hits hard when you're working in a monorepo:

The struggles are real

  1. Inconsistent tooling: One developer uses Node.js 16, another uses Node.js 20, and a third swears by Go 1.21. Suddenly, your β€œconsistent” environment isn’t so consistent anymore.

  2. Messy dependencies: Your Svelte frontend and Go backend both depend on a shared base image. Updating one image triggers a cascade of rebuilds, and if you don’t manage dependencies carefully, you’ll end up with broken builds.

  3. Painfully slow builds: Docker doesn’t automatically know which layers have changed, so you might find yourself rebuilding everything from scratchβ€”even for minor tweaks.

  4. Dev environment chaos: Whether you’re using Dev Containers, Gitpod, or just Docker CLI, keeping all these tools in sync with your latest Docker images can feel like juggling flaming torches.


The Lightbulb moment: Pants to the rescue

I needed a better way to manage Docker builds. Enter Pants: a modern build tool designed for monorepos that understands dependencies, optimizes builds, and integrates seamlessly with Git.

Here’s why Pants is my new best friend:

  • Smart Dependency Management: Pants tracks dependencies between Docker images, so if one base image changes, only the affected images are rebuilt.
  • Change Detection: Pants uses Git to detect file changes, rebuilding only what’s necessary.
  • Parallel Builds: It builds multiple images simultaneously wherever possible, saving loads of time.
  • Flexible Integration: The Docker images you create with Pants work seamlessly with Dev Containers, Gitpod, or plain Docker setups.

Think of Pants as your Docker image orchestrator, ensuring everything builds in the right order, at the right time.


The Setup: Svelte + Go in a Monorepo

To demonstrate how Pants works, let’s take a monorepo with two projects:

  1. example-svelte-project: A frontend app built with Svelte and Vite.
  2. example-go-project: A backend API written in Go with some batch processing workers.

Directory structure

Here’s what the monorepo looks like:

.
β”œβ”€β”€ example-svelte-project
β”‚   β”œβ”€β”€ docker
β”‚   β”‚   β”œβ”€β”€ common
β”‚   β”‚   β”‚   β”œβ”€β”€ ubuntu
β”‚   β”‚   β”‚   β”‚   └── Dockerfile
β”‚   β”‚   β”‚   β”œβ”€β”€ mise
β”‚   β”‚   β”‚   β”‚   └── Dockerfile
β”‚   β”‚   β”œβ”€β”€ frontend
β”‚   β”‚   β”‚   └── vite
β”‚   β”‚   β”‚       └── Dockerfile
└── example-go-project
    β”œβ”€β”€ docker
        β”œβ”€β”€ common
        β”‚   β”œβ”€β”€ ubuntu
        β”‚   β”‚   └── Dockerfile
        β”‚   β”œβ”€β”€ go
        β”‚   β”‚   └── Dockerfile
        β”œβ”€β”€ backend
        β”‚   β”œβ”€β”€ api
        β”‚   β”‚   └── Dockerfile
        β”‚   β”œβ”€β”€ batch
        β”‚   β”‚   └── Dockerfile
Enter fullscreen mode Exit fullscreen mode

Step 1: Installing Pants

First, we need to install Pants. I used Ubuntu 24.04 as my base environment.

Installation Steps

  1. Install dependencies:
   sudo apt update
   sudo apt install -y ca-certificates curl unzip python3-dev build-essential
   snap install go --classic --channel=1.22/stable
Enter fullscreen mode Exit fullscreen mode
  1. Install Pants:
   curl --proto '=https' --tlsv1.2 -sSfL 'https://static.pantsbuild.org/setup/get-pants.sh' | bash
   echo 'export PATH=~/.local/bin:$PATH' >> ~/.bashrc
   source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode
  1. Verify installation:
   pants --version
Enter fullscreen mode Exit fullscreen mode

Step 2: Defining Docker Builds with Pants

Example 1: The Base Image (ubuntu/Dockerfile)

This lightweight image serves as the foundation for all other images:

FROM ubuntu:24.04 AS base
ARG USER_NAME
RUN apt update && apt install -y sudo curl
RUN useradd -m $USER_NAME
Enter fullscreen mode Exit fullscreen mode

Define it in example-svelte-project/docker/common/ubuntu/BUILD:

docker_image(
    name="dev-common-ubuntu",
    source="Dockerfile",
    image_tags=["dev-common-ubuntu"],
    target_stage="base",
    extra_build_args={"USER_NAME": "developer"},
)
Enter fullscreen mode Exit fullscreen mode

Build it with Pants:

pants package example-svelte-project/docker/common/ubuntu:dev-common-ubuntu
Enter fullscreen mode Exit fullscreen mode

Example 2: The Dev Tooling Image (mise/Dockerfile)

This image includes tools like Node.js and Go for development:

FROM dev-common-ubuntu AS dev
RUN curl -fsSL https://get.mise-lang.sh | bash
Enter fullscreen mode Exit fullscreen mode

Define it in example-svelte-project/docker/common/mise/BUILD:

docker_image(
    name="dev-common-mise",
    source="Dockerfile",
    dependencies=[":dev-common-ubuntu"],
    image_tags=["dev-common-mise"],
)
Enter fullscreen mode Exit fullscreen mode

Build it with Pants:

pants package example-svelte-project/docker/common/mise:dev-common-mise
Enter fullscreen mode Exit fullscreen mode

Example 3: Svelte Frontend (vite/Dockerfile)

This container runs the Svelte app:

FROM dev-common-mise AS dev
WORKDIR /app
COPY . .
RUN npm install && npm run build
Enter fullscreen mode Exit fullscreen mode

Define it in example-svelte-project/docker/frontend/vite/BUILD:

docker_image(
    name="dev-frontend-vite",
    source="Dockerfile",
    dependencies=[":dev-common-mise"],
    image_tags=["dev-frontend-vite"],
)
Enter fullscreen mode Exit fullscreen mode

Step 3: Using Docker Images for Development

Now that we’ve built the Docker images with Pants, it’s time to use them in our development workflows.

1. Dev Containers (VS Code)

Define a .devcontainer/devcontainer.json file to use the Docker image as your development environment:

{
  "image": "dev-common-mise:latest",
  "extensions": [
    "dbaeumer.vscode-eslint",
    "golang.go"
  ]
}
Enter fullscreen mode Exit fullscreen mode

2. Gitpod

In your .gitpod.yml file:

image: dev-common-mise:latest
tasks:
  - init: npm install
  - command: npm run dev
Enter fullscreen mode Exit fullscreen mode

3. Local Docker Environment

Run the Docker image locally:

docker run -it dev-common-mise:latest bash
Enter fullscreen mode Exit fullscreen mode

Step 4: Optimizing the Workflow

Parallel Builds

Build multiple images at the same time:

pants package ::
Enter fullscreen mode Exit fullscreen mode

Change Detection

Rebuild only what’s changed:

pants --changed-since='HEAD' --changed-dependents=transitive package
Enter fullscreen mode Exit fullscreen mode

Why this approach works

  • Consistency: Developers work in identical environments using the same Docker images.
  • Speed: Pants optimizes builds with change detection and parallelism.
  • Flexibility: Use the same images for Dev Containers, Gitpod, or plain Docker CLI.
  • Scalability: Shared base images reduce duplication and simplify maintenance.

Example repository

GitHub logo Siddhant-K-code / example-pants

A sample repository demonstrating the usage of the Pants build system

example-pants

A sample repository demonstrating the usage of the Pants build system.

Repository structure

.
β”œβ”€β”€ example-go-project
β”‚   β”œβ”€β”€ app
β”‚   β”‚   β”œβ”€β”€ backend
β”‚   β”‚   └── frontend
β”‚   └── docker
β”‚       β”œβ”€β”€ backend
β”‚       β”‚   β”œβ”€β”€ api
β”‚       β”‚   β”‚   β”œβ”€β”€ BUILD
β”‚       β”‚   β”‚   β”œβ”€β”€ Dockerfile
β”‚       β”‚   β”‚   └── extensions.json
β”‚       β”‚   └── batch
β”‚       β”‚       β”œβ”€β”€ BUILD
β”‚       β”‚       β”œβ”€β”€ Dockerfile
β”‚       β”‚       └── extensions.json
β”‚       β”œβ”€β”€ common
β”‚       β”‚   β”œβ”€β”€ mise
β”‚       β”‚   β”‚   β”œβ”€β”€ BUILD
β”‚       β”‚   β”‚   └── Dockerfile
β”‚       β”‚   β”œβ”€β”€ rust
β”‚       β”‚   β”‚   β”œβ”€β”€ BUILD
β”‚       β”‚   β”‚   └── Dockerfile
β”‚       β”‚   └── ubuntu
β”‚       β”‚       β”œβ”€β”€ BUILD
β”‚       β”‚       β”œβ”€β”€ Dockerfile
β”‚       β”‚       └── Tiltfile
β”‚       └── frontend
β”‚           └── vite
β”‚               β”œβ”€β”€ BUILD
β”‚               β”œβ”€β”€ Dockerfile
β”‚               └── extensions.json
β”œβ”€β”€ example-svelte-project
β”‚   β”œβ”€β”€ app
β”‚   β”‚   β”œβ”€β”€ backend
β”‚   β”‚   └── frontend
β”‚   └── docker
β”‚       β”œβ”€β”€ backend
…
Enter fullscreen mode Exit fullscreen mode

For more tips and insights, follow me on Twitter @Siddhant_K_code and stay updated with the latest & detailed tech content like this.

Top comments (0)