Using Emacs for Container Development: Configuring Emacs for Podman and Docker Support

Cover Image for Using Emacs for Container Development: Configuring Emacs for Podman and Docker Support
Rahul M. Juliato
Rahul M. Juliato
#emacs#docker# podman

Turn Emacs into a powerful Docker development environment that supports both Docker and Podman workflows. This guide walks you through setting up Dockerfile editing, container management, and automated builds, all within Emacs. Whether you’re switching between Docker and Podman or managing complex containerized projects, Emacs can streamline your development process. Dive in to discover how to integrate Docker with Emacs and boost your workflow!

Introduction

Emacs is a powerhouse for development, and with the right configuration, it can even manage your Docker (or Podman) workflows without leaving the editor. In this guide, we’ll walk through setting up Emacs specifically to work with Docker and Podman. We’ll cover editing Dockerfiles, managing containers, and integrating Docker-specific language support.

External (non Emacs related) Dependencies

To use Docker or Podman effectively within Emacs, ensure the following external dependencies are installed and functioning on your system:

  • Docker: Expected to be installed and running.

  • Docker-Compose: Expected to be installed and functional.

and/or

  • Podman: Expected to be installed and running.

  • Podman-Compose: Expected to be installed and functional.

Setting the Docker Executable

In LEmacs (my own config for Emacs), I've created a flexible setup that allows you to toggle between Docker and Podman as your primary container engine. This makes it easy to adapt your workflow depending on your environment. No needs to use LEmacs by the way, I'll show here all the code needed.

(defcustom lemacs-docker-executable 'docker
  "The executable to be used with docker-mode."
  :type '(choice
           (const :tag "docker" docker)
           (const :tag "podman" podman))
  :group 'lemacs)

Explanation: This customizable variable allows you to switch between Docker and Podman in Emacs by simply updating the lemacs-docker-executable setting. It’s an efficient way to keep your setup flexible, especially if you work with both Docker and Podman environments.

The docker package provides a way to manage containers directly from Emacs. With the following setup, you can open the docker menu with C-c d and manage your containers, images, and volumes without ever leaving Emacs.

(use-package docker
  :defer t
  :ensure t
  :bind ("C-c d" . docker)
  :config
  (pcase lemacs-docker-executable
    ('docker
     (setf docker-command "docker"
           docker-compose-command "docker-compose"))
    ('podman
     (setf docker-command "podman"
           docker-compose-command "podman-compose"))))

Explanation: By setting up docker.el with a keybinding, you can quickly access Docker or Podman management commands. The lemacs-docker-executable check allows docker.el to adapt to the selected engine, setting the appropriate commands for docker-command and docker-compose-command.

Editing Dockerfiles with dockerfile-mode

Writing Dockerfiles in Emacs is made easier with dockerfile-mode, which provides syntax highlighting, indentation, and command customization. Here’s how we configure it:

(use-package dockerfile-mode
  :defer t
  :ensure t
  :config
  (pcase lemacs-docker-executable
    ('docker
     (setq dockerfile-mode-command "docker"))
    ('podman
     (setq dockerfile-docker-command "podman"))))

Explanation: This setup activates dockerfile-mode for any file named Dockerfile, providing Dockerfile-specific syntax support. Additionally, based on lemacs-docker-executable, it sets the command used to build images, either docker or podman, so you can run dockerfile-build-buffer with the correct tool.

Adding Language Support for Dockerfiles with lsp-mode

To enhance editing with features like autocompletion, diagnostics, and linting, you’ve set up lsp-mode with dockerfile-language-server-nodejs. This provides IDE-like capabilities for Dockerfile editing in Emacs.

In order to do so, just run M-x lsp-install-server RET and look for docker, of course, you have to properly setup lsp-mode first.

Explanation: With lsp-mode and dockerfile-language-server-nodejs, you gain smart language features while writing Dockerfiles, such as error checking and autocomplete. This is especially useful for complex Dockerfiles, where syntax and structure are critical for successful builds.

Setting Up yaml-mode for Docker-Compose Files

To effectively work with docker-compose.yml files, it’s beneficial to have syntax highlighting and proper indentation for YAML. We can achieve this by setting up yaml-mode in Emacs. Here's how you can configure it:

(use-package yaml-mode
  :defer t
  :ensure t
  :mode
  ("\\.yaml\\'" "\\.yml\\'")
  :custom-face
  (font-lock-variable-name-face ((t (:foreground "#cba6f7"))))
  :config)

Explanation: This setup uses use-package to ensure yaml-mode is installed and deferred until needed, activates it for .yaml and .yml files, customizes the appearance of variable names with a specific foreground color, and allows for additional configurations as required.

LAB Time!

Now that we’ve set up Emacs for Docker (or Podman), let's dive into some hands-on tasks to see how this configuration works in action.

1. Create and Edit a Dockerfile

Start by creating a new Dockerfile in Emacs. Open a new buffer and save it with the filename Dockerfile. Once saved, dockerfile-mode will kick in automatically, giving you syntax highlighting and other Dockerfile-specific features.

Given we have a React project (it could've been any other project, this is just an example) we could containerize it by creating this Dockerfile.

FROM node:21-alpine

WORKDIR /app

ENV PATH /app/node_modules/.bin:$PATH

COPY package.json ./
COPY package-lock.json ./
RUN npm install --silent
RUN npm install react-scripts@5.0.0 -g --silent

COPY . ./

CMD ["npm", "start"]

Tip: With lsp-mode configured for Dockerfiles, you’ll get autocompletion, syntax checking, and even inline diagnostics, making it easier to spot issues as you write.

docker

2. Build the Dockerfile

After saving your Dockerfile, it’s time to build it.

Of course, you can open shell or eshell and run something like:

docker build -t my-app .

or

podman build -t my-app .

But since we have the dockerfile-mode set. We can simply issue M-x dockerfile-build-buffer or M-x dockerfile-build-no-cache-buffer.

docker

You'll be prompted for an image name, like docker-app.

docker

And a new buffer with the build status will appear.

docker

Wait for it until it is finished.

docker

3. Run a Container with Custom Options

After building your image, we could, once again, issue some shell command to have it running, like:

docker run -p 3000:3000 docker-app

or

podman run -p 3000:3000 docker-app

Now docker package shines again, running M-x docker RET we get:

docker

The bottom buffer is a transient menu, much like what you get when opening magit, this means there's no need to focus the buffer or navigate your cursor inside it.

We can check our built images hitting i.

docker

In our case we have an node image from what our image derives, and our nice docker-app image.

You can also mark it with m, unmark it with u, spin up what is marked, delete and so on, at any sub-menu just hit ? for a list of options.

Let's run our recently built image. Put the cursor on the line with our docker-app. Hit ? to see the options:

docker

Now hit R to run it, and add any arguments, like p for port forwarding:

docker

R again and RET.

And there we have it, our app is running.

docker

Issuing M-x docker RET again we can see the containers status:

docker

Hitting c will give us a list with all containers and its statuses:

docker

Again, hitting ? prompts us with many options, keep your time and explore it!

docker

For now, I'll just stop it with O.

docker

4. Working with Docker-Compose

While docker.el makes it easy to manage individual Docker containers, using docker-compose within Emacs allows us to orchestrate multiple containers at once-perfect for projects with multiple services like databases, backends, and frontends.

First, ensure you have a docker-compose.yml file in your project directory. Here’s an example:

version: "3.7"

services:
  docked-app:
    container_name: docked-app
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ".:/app"
      - "/app/node_modules"
    ports:
      - 3001:3000
    environment:
      - CHOKIDAR_USEPOLLING=true

Notice you can also install an LSP server to help with editing this sort of file, like yamlls or any other of your liking.

Also notice this is a yaml file and it is not using our previous installed dockerfile-mode but yaml-mode.

docker

Once again, we could issue a shell command like:

docker-compose up -d

or

podman-compose up -d

Fortunately docker package also provides us with a nice frontend for managing docker-compose.

Issue M-x docker RET once again, see the last option C.

docker

We are presented with all sorts of options regarding docker-compose:

docker

We are choosing B for build, and them A for all services. Note this example has one service, but docker-compose really shines in a use case of several services, like backend-api, plus database, plus frontend, and so on.

Again, we wait for a complete build.

docker

When finished we again issue M-x docker RET, C for compose and R to run.

docker

Again, add any other custom configuration and hit R RET to run it.

And there we have it, our own little cluster is up!

From now on you can figure out what to do 😉.

All the covered code

Here it is all the presented code, for a quicker copy-paste.

(defcustom lemacs-docker-executable 'docker
  "The executable to be used with docker-mode."
  :type '(choice
           (const :tag "docker" docker)
           (const :tag "podman" podman))
  :group 'lemacs)

(use-package docker
  :defer t
  :ensure t
  :bind ("C-c d" . docker)
  :config
  (pcase lemacs-docker-executable
    ('docker
     (setf docker-command "docker"
           docker-compose-command "docker-compose"))
    ('podman
     (setf docker-command "podman"
           docker-compose-command "podman-compose"))))

(use-package dockerfile-mode
  :defer t
  :ensure t
  :config
  (pcase lemacs-docker-executable
    ('docker
     (setq dockerfile-mode-command "docker"))
    ('podman
     (setq dockerfile-docker-command "podman"))))

(use-package yaml-mode
  :defer t
  :ensure t
  :mode
  ("\\.yaml\\'" "\\.yml\\'")
  :custom-face
  (font-lock-variable-name-face ((t (:foreground "#cba6f7"))))
  :config)

Conclusion

In this blog post, we explored how to effectively set up Emacs for Docker and Docker-Compose management, leveraging the power of docker.el to streamline container orchestration within our development environment. From building Docker images to running and managing containers seamlessly, Emacs provides an efficient interface that enhances productivity and simplifies workflows.

By integrating yaml-mode, we ensured that our docker-compose.yml files are easy to read and edit, allowing us to focus on coding rather than configuration. With these tools at our disposal, we can harness the full potential of containerization directly from our favorite text editor.

We encourage you to explore the myriad options available within docker.el and Docker-Compose to tailor your Emacs setup even further. Happy coding, and may your containers run smoothly!

Edit

2024-11-05: Updated from when to pcase logic style and removed not required mode load for dockerfile-mode. References, suggestions, credits: Reddit (r/emacs)