← Back to DevBytes

Emacs Docker Integration: Complete Guide

What Is Emacs Docker Integration

Emacs Docker integration refers to the ecosystem of Emacs packages and built-in capabilities that allow developers to interact with Docker directly from within their editor. Instead of switching to a terminal to run docker ps, docker build, or docker-compose up, you can manage containers, images, volumes, networks, and even edit files inside running containers — all without leaving Emacs.

This integration typically spans several layers:

The most popular packages that power this experience include docker.el (by Akira Komamura, often referred to as docker on MELPA), docker-compose.el, dockerfile-mode, and Emacs's own TRAMP subsystem with Docker-specific methods.

Why Docker Integration in Emacs Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

For developers who live in Emacs, context switching is the enemy of productivity. Every time you leave the editor to run a Docker command in a terminal, you break your flow, lose your place, and spend cognitive energy reorienting yourself. Docker integration eliminates these context switches entirely.

Here are the concrete benefits:

Whether you are debugging a misbehaving microservice, iterating on a Dockerfile, or managing a complex compose stack, having Docker at your fingertips inside Emacs transforms the experience from fragmented to fluid.

Core Packages Overview

Before diving into usage, let's survey the key packages you'll want to install. All are available on MELPA:

With these installed, your Emacs becomes a full-fledged Docker management console.

Setting Up docker.el

The docker.el package is the cornerstone of Emacs Docker integration. Install it via MELPA, then add a minimal configuration to your init file:

(use-package docker
  :ensure t
  :bind ("C-c d" . docker-menu))

Alternatively, for a more comprehensive setup with transient menus (which the package uses extensively):

(use-package docker
  :ensure t
  :config
  ;; Enable transient menu for quick access
  (define-key docker-menu-mode-map (kbd "?") 'docker-help)
  ;; Refresh intervals for live buffers (in seconds)
  (setq docker-container-refresh-interval 5)
  (setq docker-image-refresh-interval 10)
  :bind
  ("C-c d" . docker-menu)
  ("C-c d c" . docker-containers)
  ("C-c d i" . docker-images)
  ("C-c d v" . docker-volumes)
  ("C-c d n" . docker-networks))

Once configured, press C-c d to bring up the top-level Docker menu. From there, you can drill into containers, images, volumes, or networks.

The docker.el Transient Menu

The package uses transient (the same library powering Magit) to present contextual actions. When you open the Docker menu, you'll see something like:

Docker menu
------------
  c   Containers
  i   Images
  v   Volumes
  n   Networks
  ?   Help

Selecting c for containers opens a tabulated list buffer showing all containers with columns for ID, image, status, ports, and names. From this buffer, you can mark containers and execute batch operations.

Working with Docker Images

To list all images, use M-x docker-images or press i from the Docker menu. This opens a dedicated buffer in tabulated-list-mode:

REPOSITORY        TAG       IMAGE ID      CREATED       SIZE
nginx             latest    2b7c8c1e9f0   2 days ago    187MB
node              18-alpine 9a7b5c3d...   3 days ago    118MB
my-app            v2        f4e8a9b2...   5 hours ago   245MB

From this buffer, you can perform various actions using single-key commands:

Here is an example of pulling an image programmatically from your init file or via M-::

(docker-run "pull" "python:3.11-slim")

The image list buffer auto-refreshes based on docker-image-refresh-interval, so newly pulled images appear without manual reloading.

Managing Containers

The container list is where you'll spend most of your time. Open it with M-x docker-containers or c from the Docker menu. The buffer displays all containers (use C-c C-s to toggle between all and running-only):

ID          IMAGE         STATUS          PORTS                    NAMES
a1b2c3d4    nginx         Up 3 hours      0.0.0.0:8080->80/tcp    web-server
e5f6g7h8    node:18       Exited (0)       -                        build-step
i9j0k1l2    postgres:15   Up 2 days        0.0.0.0:5432->5432/tcp  db

Key actions available in the container buffer:

Attaching to a Container Shell

One of the most powerful features: press e on a running container, and Emacs prompts for the command to execute (defaults to /bin/bash or /bin/sh). It then opens a buffer in comint-mode — meaning you get full shell interaction with Emacs readline capabilities, history, and output searchability:

;; Equivalent programmatic invocation:
(docker-exec "web-server" "/bin/bash")

Inside this shell buffer, you can use C-c C-c to send signals, M-p/M-n for command history, and all standard comint navigation. The output is in a regular Emacs buffer, so you can copy, search, and save it.

Viewing Container Logs

Press l on a container to open a log buffer. This runs docker logs --follow in the background and streams output into Emacs. The buffer updates in near real-time. You can kill the log stream with C-c C-k (comint interrupt). For programmatic access:

(docker-logs "web-server" '("-f" "--tail=100"))

Docker Compose Integration

The docker-compose.el package extends the same transient-menu philosophy to Docker Compose stacks. Install it and bind a key:

(use-package docker-compose
  :ensure t
  :bind ("C-c C-d" . docker-compose-menu))

When you invoke docker-compose-menu, it scans for a docker-compose.yml or compose.yaml file in the current directory (or a parent directory) and presents available services:

Docker Compose menu (project: myapp)
-------------------------------------
Services:
  web        (node app, port 3000)
  api        (python fastapi, port 8000)
  db         (postgres 15)
  redis      (redis 7)

Actions:
  u   Up all services
  d   Down all services
  r   Restart services
  l   Logs
  b   Build
  p   Pull

You can operate on individual services or the entire stack:

For scripted compose operations, you can use the underlying functions directly:

;; Bring up the entire compose stack
(docker-compose-up)

;; Bring down and remove volumes
(docker-compose-down '("-v"))

;; Run a command in the web service
(docker-compose-exec "web" "npm test")

Working with Multiple Compose Files

If your project uses multiple compose files (e.g., docker-compose.yml and docker-compose.override.yml), docker-compose.el respects the standard Docker Compose file discovery order. You can also specify files explicitly:

(docker-compose-up "-f" "docker-compose.prod.yml")

Dockerfile Mode

dockerfile-mode provides syntax highlighting, indentation rules, and snippets for Dockerfiles. Install it and it automatically activates for files named Dockerfile or *.dockerfile:

(use-package dockerfile-mode
  :ensure t
  :mode (("Dockerfile\\'" . dockerfile-mode)
         ("\\.dockerfile\\'" . dockerfile-mode)))

Key features of dockerfile-mode:

Here's an example Dockerfile with proper highlighting in Emacs:

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

With dockerfile-mode active, each instruction gets distinct face colors, making multi-stage builds easy to parse visually.

Building from a Dockerfile

While editing a Dockerfile, you can build it directly. Use M-x docker-build from the Dockerfile buffer, or use the container/image menu. The build output appears in a compilation-mode buffer, so you can navigate errors with next-error (C-x \`):

;; Build with a specific tag from the current directory
(docker-build "." "-t" "myapp:latest")

You can bind a key for quick builds from Dockerfile buffers:

(add-hook 'dockerfile-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c C-b")
                           (lambda ()
                             (interactive)
                             (docker-build "." "-t" "myapp:latest")))))

TRAMP Docker Integration — Editing Files Inside Containers

TRAMP (Transparent Remote Access, Multiple Protocol) is Emacs's built-in system for accessing remote files. Modern TRAMP includes a Docker method that lets you open files inside containers using a special path syntax:

/docker:container-name:/path/to/file
;; or with a specific user
/docker:user@container-name:/path/to/file

To open /etc/nginx/nginx.conf inside a running container named web-server:

C-x C-f /docker:web-server:/etc/nginx/nginx.conf RET

TRAMP transparently copies the file to a temporary local location, opens it in Emacs, and writes changes back to the container on save. This works with all Emacs modes — syntax highlighting, completion, linters, and even language servers function normally because the buffer sees a local file path while TRAMP handles the synchronization.

Browsing Container Filesystems with Dired

You can also browse a container's filesystem using Dired:

/docker:web-server:/var/log/

This opens a Dired buffer showing the container's /var/log/ directory. You can navigate, copy files between container and host, delete, rename, and change permissions — all through Dired's familiar interface.

Setting Up TRAMP for Docker

TRAMP's Docker support is usually available out of the box in Emacs 27+. To verify, check your TRAMP methods:

(require 'tramp)
;; List available methods — look for "docker"
(tramp-docker-method-p "docker")

If you need to add Docker explicitly to TRAMP methods:

(add-to-list 'tramp-methods
             '("docker"
               (tramp-login-program "docker")
               (tramp-login-args (("exec") ("-it") ("%u") ("%h") ("/bin/sh")))
               (tramp-copy-program "docker")
               (tramp-copy-args (("cp") ("%u:%f") ("%k")))
               (tramp-copy-from-args (("cp") ("%k") ("%u:%f")))))

For the docker-tramp.el package (which adds convenience wrappers):

(use-package docker-tramp
  :ensure t
  :config
  (setq docker-tramp-use-names t))  ;; use container names, not IDs

TRAMP Docker Completion

Modern TRAMP offers completion for container names. When you type /docker: and press TAB, Emacs queries Docker for running containers and presents them as completion candidates. This makes navigating into containers as fast as navigating local files.

Building and Running Containers from Emacs

Beyond the interactive menus, docker.el provides functions for programmatic container operations. These are useful in custom commands, macros, or integration with project workflows.

Running a Container

;; Run an nginx container detached with port mapping
(docker-run "run" "-d" "--name" "web" "-p" "8080:80" "nginx:latest")

;; Run interactively with a TTY
(docker-run "run" "-it" "--rm" "alpine:latest" "/bin/sh")

Building an Image

;; Build with tags and build args
(docker-build "."
              "-t" "myapp:latest"
              "-t" "myapp:v2.0"
              "--build-arg" "NODE_ENV=production")

Cleaning Up Resources

;; Remove all stopped containers
(docker-run "container" "prune" "-f")

;; Remove dangling images
(docker-run "image" "prune" "-f")

;; Full system prune
(docker-run "system" "prune" "-af")

Custom Docker Command Wrapper

You can wrap any docker command and capture its output in a buffer:

(defun my-docker-inspect-container (container-name)
  "Inspect CONTAINER-NAME and display formatted output."
  (interactive "sContainer name: ")
  (let ((buf (get-buffer-create "*docker-inspect*")))
    (with-current-buffer buf
      (erase-buffer)
      (insert (shell-command-to-string
               (format "docker inspect %s" container-name)))
      (json-mode)
      (goto-char (point-min)))
    (display-buffer buf)))

Best Practices for Emacs Docker Integration

1. Use Transient Menus as Your Primary Interface

The transient-based menus in docker.el and docker-compose.el are designed for discoverability. Rather than memorizing dozens of keybindings, learn the menu flow: C-c d → choose resource → single-key action. This reduces cognitive load and keeps you focused on the task.

2. Bind Docker Commands to Project-Specific Keys

If you work on a project with frequent Docker operations, create project-specific keybindings using projectile or project.el:

;; With projectile
(define-key projectile-mode-map (kbd "C-c p D")
  (lambda () (interactive) (docker-compose-up)))

;; With directory-local variables
;; In .dir-locals.el:
((dockerfile-mode . ((eval . (local-set-key (kbd "C-c C-b")
                              (lambda ()
                                (interactive)
                                (docker-build "." "-t" "myapp")))))))

3. Integrate Log Viewing into Your Debugging Workflow

Container log buffers in Emacs are searchable, bookmarkable, and can be saved. When debugging, open logs with l from the container buffer, then use M-x occur to find error patterns, or M-x highlight-regexp to color-code important log lines. This is far more powerful than terminal-based log tailing.

4. Leverage TRAMP for Configuration Editing

Instead of exec-ing into a container to edit config files with vi/nano, use TRAMP: /docker:container:/etc/config.yml. You get full Emacs editing capabilities — multiple cursors, regex search/replace, undo tree, and all your custom modes. Save the buffer, and the container picks up changes immediately (or after a restart, depending on the service).

5. Combine Docker with Org Mode for Runbooks

For operational runbooks, embed Docker commands in Org mode code blocks:

#+begin_src sh :dir /ssh:prod-server|docker:web-container:/app
  npm run migrate
#+end_src

This executes the migration command inside the production container, with the working directory set via TRAMP multi-hop syntax.

6. Set Reasonable Refresh Intervals

The live-updating container and image buffers can cause unnecessary Docker API calls if refresh intervals are too short. Set them based on your workflow:

(setq docker-container-refresh-interval 10)  ;; seconds
(setq docker-image-refresh-interval 30)
(setq docker-volume-refresh-interval 60)

For low-resource environments, disable auto-refresh and use manual refresh (g in the buffer).

7. Use docker-compose.el for Multi-Service Workflows

When working with microservices, docker-compose.el shines. Bind it prominently and use the per-service log viewing. For development, map a key to restart a single service without bringing down the entire stack:

(defun restart-service (service)
  (interactive "sService: ")
  (docker-compose-restart service))

8. Keep an Emacs Dashboard Buffer

Create a dedicated frame or window configuration that shows containers, images, and compose services simultaneously. Emacs's window management lets you tile these buffers:

;; Example: split frame into containers + logs
(split-window-horizontally)
(docker-containers)
(split-window-vertically)
(docker-logs "web-server")

Save this window configuration with M-x window-configuration-to-register and restore it with C-x r j.

9. Handle Docker Socket Permissions Gracefully

If your Docker daemon requires sudo, configure TRAMP and docker.el to use it. For TRAMP:

(add-to-list 'tramp-methods
             '("docker-sudo"
               (tramp-login-program "sudo")
               (tramp-login-args (("docker") ("exec") ("-it") ("%u") ("%h") ("/bin/sh")))
               (tramp-copy-program "sudo")
               (tramp-copy-args (("docker") ("cp") ("%u:%f") ("%k")))))

Then access containers via /docker-sudo:container:/path.

10. Version-Control Your Docker Configurations

With Emacs, your Dockerfiles, compose files, and related scripts all live in the same version-controlled project. Use Magit to track changes to Docker configurations alongside application code. This unified version control is a significant advantage over scattered terminal-based workflows.

Conclusion

Emacs Docker integration transforms the editor from a code-writing tool into a complete container orchestration environment. Through docker.el's transient menus, you gain rapid keyboard-driven management of images, containers, volumes, and networks. docker-compose.el extends this to multi-service stacks with the same intuitive interface. TRAMP's Docker method dissolves the boundary between host and container filesystems, letting you edit container files with full Emacs power. And dockerfile-mode ensures your Dockerfiles benefit from syntax highlighting, completion, and linting.

The key insight is that these tools compose together. You can browse a container's filesystem with Dired, open a configuration file with TRAMP, edit it with your fully customized Emacs, save it back to the container, rebuild the image from the Dockerfile buffer, restart the compose service, and tail the logs — all in a single Emacs session, without ever touching a separate terminal. For developers committed to the Emacs way, this integration is not merely convenient; it fundamentally elevates the Docker development experience by placing it inside the most powerful text manipulation environment ever created.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles