3
$\begingroup$

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?

$\endgroup$
3
$\begingroup$

TL;DR:

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 \
        -DWITH_INSTALL_PORTABLE=ON \
        -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.

#!/bin/sh

### BEGIN INIT INFO
# 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
### END INIT INFO

NAME="xvfb"
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
APPDIR="/"
APPBIN="/usr/bin/Xvfb"
APPARGS=":0 -screen 0 1920x1080x24"
USER="xvfb"
GROUP="xvfb"

# 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}
  done
  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
done
  [ -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
start)
  start
  ;;
stop)
  stop
  ;;
restart)
  stop
  start
  ;;
status)
  status
  ;;
*)
  echo "Usage: $NAME {start|stop|restart|status}" >&2
  exit 1
  ;;
esac

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 \
    libgl1
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.