A plotting process can fail before it reaches savefig() when its backend expects a window system that the host does not provide. Scheduled jobs, CI runners, containers, and remote shells need a renderer that never depends on a desktop display.

Import order controls this script-level choice. Calling matplotlib.use(“Agg”) before importing matplotlib.pyplot fixes the backend before pyplot creates or manages any figures.

The Agg backend supplies the non-interactive FigureCanvasAgg canvas and writes raster images without an X or Wayland connection. A display-less run can therefore identify that canvas and read the saved PNG back from disk in the same process.

Steps to select Agg before importing pyplot:

  1. Start headless_plot.py with Agg selected before the pyplot import.
    headless_plot.py
    from pathlib import Path
     
    import matplotlib
     
    matplotlib.use("Agg")
     
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_agg import FigureCanvasAgg
  2. Create the plotting figure with one axes object below the imports.
    fig, ax = plt.subplots(figsize=(8, 4.5), layout="constrained")
  3. Bind the figure's FigureCanvasAgg instance to canvas.
    canvas = fig.canvas
  4. Reject any canvas class other than FigureCanvasAgg.
    if not isinstance(canvas, FigureCanvasAgg):
        raise RuntimeError(f"Expected FigureCanvasAgg, got {type(canvas).__name__}")

Prove Agg rendering without a display:

  1. Export a labeled line plot from the figure to headless-plot.png.
    output = Path("headless-plot.png")
     
    ax.plot([1, 2, 3, 4], [2, 4, 3, 5], marker="o")
    ax.set_title("Headless Matplotlib")
    ax.set_xlabel("Run")
    ax.set_ylabel("Value")
     
    fig.savefig(output, dpi=120)
  2. Read headless-plot.png back into Matplotlib.
    image = plt.imread(output)
  3. Reject a decoded PNG whose dimensions differ from 960 by 540 pixels.
    height, width = image.shape[:2]
    if (width, height) != (960, 540):
        raise RuntimeError(f"Expected 960 x 540 pixels, got {width} x {height}")
  4. Report the backend, canvas class, filename, and pixel dimensions.
    canvas_name = type(canvas).__name__
    print(f"backend: {matplotlib.get_backend()}")
    print(f"canvas: {canvas_name}")
    print(f"saved: {output.name}")
    print(f"pixels: {width} x {height}")
  5. Run headless_plot.py without desktop display variables to verify the Agg canvas and PNG readback.
    $ env -u DISPLAY -u WAYLAND_DISPLAY python3 headless_plot.py
    backend: Agg
    canvas: FigureCanvasAgg
    saved: headless-plot.png
    pixels: 960 x 540