I'm setting up rendering in a Kubernetes cluster and am using Blender as a Python module to run a script inside a Docker container.

Cycles rendering works as it should, not very fast, but it works just fine.

I wanted to try using Eevee as a renderer to reduce the time taken to render an animation (even if I vastly prefer the quality of Cycles, but we were talking months to render my animation using Cycles, so of course I wanna try out Eevee instead). However, to my chagrin, I discovered that Eevee can't render in headless mode!

I'm also having issues getting bpy to work when downloaded prebuilt and it seems out of date.

How can I get Eevee to render in a headless Docker container, and how do I build bpy for it?


1 Answer 1



To get bpy running just use the Linux instructions for building Blender, change the install location to its own folder, e.g. /opt/blender/site-packages, then in your production container simply use COPY --from=blender /opt/blender/site-packages/ /path/to/your/site-packages.

Using xvfb we can create a framebuffer that acts as a regular display inside a headless environment. Simply install it and create a system service that starts it. I've tested using a tiny display, 300x300x8, and it seemed to have no effect on the end result of the render, you may be able to go even smaller and less color depth. I'd love to try 1x1x1 because that is essentially attaching a lightbulb to your computer and I love the idea that Eevee can't run without a lightbulb attached.

Long story:

I may or may not upload what I'm gonna show you here to Docker Hub so you don't have to do this yourself (on my computer it takes like 20 minutes to build the blender container) but I'll just walk you through it here since it's actually fairly easy.

I'll be using debian:buster-slim, I recommend using the same environment as you'll be using in production for building Blender

First set up a builder for bpy so you don't have to bloat your production version with unnecessary stuff. We're more or less just straight up using the official documentation on how to build Blender as a python module here.

Make a Dockerfile (or whatever.dockerfile) that contains the following:

# Use debian:buster-slim because it's small enough and runs faster than alpine
FROM debian:buster-slim as blender

WORKDIR /opt/blender

RUN export DEBIAN_FRONTEND=noninteractive && \
# Enables some additional packages that are needed for Blender to work in debian:buster-slim
    echo 'deb http://deb.debian.org/debian testing main contrib non-free' > /etc/apt/sources.list.d/testing.list && \
    apt update && \
    apt install -yq --no-install-recommends \
        build-essential \
        apt-transport-https \
        ca-certificates \
        git \
        subversion \
        cmake \
        python3 \
        libx11-dev \
        libxxf86vm-dev \
        libxcursor-dev \
        libxi-dev \
        libxrandr-dev \
        libxinerama-dev \
        libglew-dev && \
# Git complains that it can't clone blender if you don't run this
    update-ca-certificates && \
    git clone https://git.blender.org/blender.git blender && \
# Check out linux dependency
    mkdir lib && \
    cd lib && \
    svn checkout https://svn.blender.org/svnroot/bf-blender/trunk/lib/linux_centos7_x86_64 && \
    cd ../blender && \
# Get more dependencies
    make update && \
# Build bpy
    make bpy && \
    cd ../build_linux_bpy && \
# Change install path
    cmake ../blender \
        -DCMAKE_INSTALL_PREFIX=/opt/blender/site-packages && \
# Install finished module to /opt/blender/site-packages
    make install && \
# Do some cleanup because why the hell not
    apt autoremove -yq && \
    apt clean && \
    rm -rf /var/lib/{apt,dpkg,cache,log}/ && \
    rm -rf /opt/blender/{blender,lib,build_linux_bpy}/

That's all there is for the blender builder image! It will have all your files available inside /opt/blender/site-packages and be ready for use if you just copy the contents of that folder into whatever folder your python runtime is using.

Your production image should be placed in the same file as above:

# Use same image as when you built `bpy`
FROM debian:buster-slim
# Copy over files from the `blender` image
COPY --from=blender /opt/blender/site-packages/ /path/to/site-packages/

That's all that's needed to build bpy for your container. Next we'll look into how to get a display attached to your container!

I use debian:buster-slim which uses SysVinit, which is a tiny bit annoying to set up a service for. I've included the full source code I use below. If you use another image, like ubuntu, you'll have a different init system, probably systemd, which has different instructions for how to create a service for it, but that's far more readily available so simply google how to create a service for it.

The important part for your service is that it runs something along the following: /usr/bin/Xvfb :0 -screen 0 800x600x8

Create a file simply called xvfb with the following contents, no extension. Credit for the original code goes to whoever created the following site.


# Provides:          xvfbdaemon
# Required-Start:    $local_fs $network $syslog
# Required-Stop:     $local_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example
# Description:       Example start-stop-daemon - Debian

APPARGS=":0 -screen 0 1920x1080x24"

# Include functions
set -e
. /lib/lsb/init-functions

start() {
  printf "Starting '$NAME'... "
  start-stop-daemon --start --chuid "$USER:$GROUP" --background --make-pidfile --pidfile /var/run/$NAME.pid --chdir "$APPDIR" --exec "$APPBIN" -- $APPARGS || true
  printf "done\n"

#We need this function to ensure the whole process tree will be killed
killtree() {
  local _pid=$1
  local _sig=${2-TERM}
  for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
    killtree ${_child} ${_sig}
  kill -${_sig} ${_pid}

stop() {
  printf "Stopping '$NAME'... "
  [ -z $(cat /var/run/$NAME.pid 2>/dev/null) ] ||
while test -d /proc/$(cat /var/run/$NAME.pid); do
  killtree $(cat /var/run/$NAME.pid) 15
  sleep 0.5
  [ -z $(cat /var/run/$NAME.pid 2>/dev/null) ] || rm /var/run/$NAME.pid
  printf "done\n"

status() {
  status_of_proc -p /var/run/$NAME.pid "" $NAME && exit 0 || exit $?

case "$1" in
  echo "Usage: $NAME {start|stop|restart|status}" >&2
  exit 1

exit 0

Put the following inside your Dockerfile as well somewhere to enable the service:

COPY xvfb /etc/init.d/
RUN chmod +x /etc/init.d/xvfb && \
    useradd xvfb && \
    update-rc.d xvfb defaults && \
    echo "export DISPLAY=:0" >> ~/.bashrc

Additionally, you gotta install some dependencies:

RUN apt install -yq \
# `xvfb` dependencies
    xorg \
    openbox \
    xvfb \
# `blender` dependencies
    libxxf86vm1 \
    libxfixes3 \
  • $\begingroup$ Hey, the screen WHB can no longer have a bit depth of 8. GLX doesn't support it. That being said, a WHB of 1x1x24 works completely fine! So, not a lightbulb, but Eevee can run if you attach an RGB LED! I am wondering though, if maybe running Xvfb in the project's resolution might be more performant for some reason. $\endgroup$
    – Jan Novák
    Commented Mar 31, 2022 at 15:16
  • $\begingroup$ @JanNovák Good to know :) According to my brief experiments there was no performance gain, if anything there was a slight performance loss, but I didn't try using the projects resolution, nor did I try extensively enough to say definitively that there's no potential gain from tweaking the values $\endgroup$
    – Simon Hyll
    Commented Jul 29, 2022 at 9:23
  • $\begingroup$ I would expect there would be at least some memory savings because of the extremely tiny frame buffer (you'll still have the full memory consumption of the render itself, because that is completely independent from the frame buffer), but that depends entirely on how Xvfb actually allocates it. p.s.: The reason I was interested in this is because I was testing rendering on headless ARM, and wanted to see how much time I could shave off by switching a project to Eevee (since Cycles wasn't needed anyways) - time savings were significant, though not as significant as compared to HW Accel. $\endgroup$
    – Jan Novák
    Commented Sep 4, 2022 at 14:21
  • $\begingroup$ Just want to mention that you can now install bpy through pip, so you don't need a builder image anymore. It's a 300MB prebuilt pip package. $\endgroup$ Commented Sep 27, 2023 at 8:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .