Introduction

Matplotlib's default backend tries to open a GUI window for rendering figures. On headless servers (CI/CD runners, Docker containers, AWS Lambda, SSH sessions without X11 forwarding), this fails with _tkinter.TclError: no display name and no $DISPLAY environment variable. The fix is to switch to a non-interactive backend like Agg that renders directly to image files without requiring a display server. Additional issues include missing font caches, CJK character rendering failures, and font family warnings that corrupt generated images.

Symptoms

bash
_tkinter.TclError: no display name and no $DISPLAY environment variable

Or:

bash
_tkinter.TclError: couldn't connect to display ":0"

Or font-related errors:

bash
UserWarning: Glyph 20013 (\N{CJK UNIFIED IDEOGRAPH-4E2D}) missing from font(s) DejaVu Sans.

Common Causes

  • Default backend requires X11: TkAgg or Qt5Agg need a display server
  • DISPLAY not set: SSH session or container without X11
  • Font cache outdated: New fonts installed but cache not rebuilt
  • Missing font packages: Docker image lacks fonts for CJK or special characters
  • plt.show() called in scripts: plt.show() tries to open GUI window
  • Matplotlib imported before backend set: Backend must be set before first import of pyplot

Step-by-Step Fix

Step 1: Set Agg backend before importing pyplot

```python import matplotlib matplotlib.use('Agg') # MUST be before pyplot import

import matplotlib.pyplot as plt import numpy as np

# Now all rendering goes to files, no display needed fig, ax = plt.subplots() ax.plot([1, 2, 3], [4, 5, 6]) fig.savefig('output.png', dpi=150, bbox_inches='tight') plt.close(fig) # Free memory ```

Step 2: Configure in matplotlibrc for server deployment

``` # /etc/matplotlibrc or ~/.config/matplotlib/matplotlibrc backend: Agg figure.dpi: 150 savefig.dpi: 150 savefig.bbox: tight

# Font configuration for CJK font.family: sans-serif font.sans-serif: DejaVu Sans, Arial Unicode MS, SimHei, sans-serif axes.unicode_minus: False ```

Step 3: Install fonts in Docker for CJK support

```dockerfile FROM python:3.11-slim

# Install CJK fonts RUN apt-get update && apt-get install -y \ fonts-noto-cjk \ fonts-dejavu-core \ && rm -rf /var/lib/apt/lists/*

# Rebuild font cache RUN fc-cache -fv

# Set matplotlib config RUN mkdir -p /root/.config/matplotlib RUN echo -e "backend: Agg\nfont.family: sans-serif\nfont.sans-serif: Noto Sans CJK SC, DejaVu Sans" > /root/.config/matplotlib/matplotlibrc

# Verify RUN python -c "import matplotlib; print(matplotlib.get_cachedir())" ```

Prevention

  • Set matplotlib.use('Agg') at the top of every script that runs on servers
  • Use plt.close(fig) after saving to free memory, especially in batch processing
  • Install font packages in Docker images that need CJK or special character support
  • Rebuild font cache with fc-cache -fv after installing fonts
  • Use figure context managers or explicit plt.close() to prevent memory leaks
  • Test headless rendering in CI by running figure generation as part of test suite
  • Consider using plotly or seaborn with Agg backend for production chart generation