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
_tkinter.TclError: no display name and no $DISPLAY environment variableOr:
_tkinter.TclError: couldn't connect to display ":0"Or font-related errors:
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