Context
I am trying to make Plotly + Kaleido/Choreographer work inside a Docker image based on AlmaLinux 9.6 minimal, in order to export Plotly figures as PNG images.
Installing Chromium through dnf/EPEL was problematic because of proxy issues, so I switched to the recommended Kaleido/Choreographer installation command:
This installs Chrome at:
/app/.local/share/choreographer/deps/chrome-linux64/chrome
Runtime dependencies installed
The following Chrome runtime dependencies were installed:
RUN microdnf install -y \
nss \
nspr \
dbus-libs \
at-spi2-atk \
cups-libs \
libXcomposite \
libXdamage \
libXfixes \
libXrandr \
mesa-libgbm \
libdrm \
libxkbcommon \
pango \
cairo \
alsa-lib \
gtk3 \
xdg-utils \
liberation-fonts \
&& microdnf clean all
Then this command:
ldd /app/.local/share/choreographer/deps/chrome-linux64/chrome | grep "not found"
returns nothing.
Sandbox issue identified
Chrome only works with --no-sandbox.
This command works:
/app/.local/share/choreographer/deps/chrome-linux64/chrome \
--headless \
--disable-gpu \
--disable-dev-shm-usage \
--no-sandbox \
--dump-dom https://example.com
This command fails without --no-sandbox:
/app/.local/share/choreographer/deps/chrome-linux64/chrome \
--headless \
--disable-gpu \
--disable-dev-shm-usage \
--dump-dom https://example.com
Error:
FATAL:content/browser/zygote_host/zygote_host_impl_linux.cc:128] No usable sandbox!
Wrapper added around Chrome
Since Choreographer was directly launching:
/app/.local/share/choreographer/deps/chrome-linux64/chrome
I renamed the actual Chrome binary to chrome.real and replaced chrome with a wrapper.
Wrapper:
#!/bin/sh
exec /app/.local/share/choreographer/deps/chrome-linux64/chrome.real --no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage "$@"
Dockerfile section:
RUN mv /app/.local/share/choreographer/deps/chrome-linux64/chrome \
/app/.local/share/choreographer/deps/chrome-linux64/chrome.real \
&& printf '%s\n' \
'#!/bin/sh' \
'exec /app/.local/share/choreographer/deps/chrome-linux64/chrome.real --no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage "$@"' \
> /app/.local/share/choreographer/deps/chrome-linux64/chrome \
&& chmod +x /app/.local/share/choreographer/deps/chrome-linux64/chrome \
&& chmod +x /app/.local/share/choreographer/deps/chrome-linux64/chrome.real \
&& chown app:app /app/.local/share/choreographer/deps/chrome-linux64/chrome \
&& chown app:app /app/.local/share/choreographer/deps/chrome-linux64/chrome.real
A Chrome smoke test through the wrapper works:
/app/.local/share/choreographer/deps/chrome-linux64/chrome \
--headless \
--disable-gpu \
--dump-dom file:///tmp/chrome-test.html
It returns the expected HTML content, although Chrome logs some DBus/OOM warnings, which do not seem to be blocking.
The point where image export works
When opening a shell inside the container as user app, the following minimal Plotly/Kaleido export works:
python - <<'PY'
import os
import plotly.graph_objects as go
print("BROWSER_PATH =", os.environ.get("BROWSER_PATH"))
fig = go.Figure(data=[go.Bar(x=["A", "B"], y=[1, 2])])
fig.write_image("/tmp/test-kaleido.png")
print("Kaleido OK")
PY
Output:
BROWSER_PATH = /app/.local/share/choreographer/deps/chrome-linux64/chrome
Kaleido OK
So, when run manually in an interactive shell as user app, PNG export works correctly.
Remaining issue inside the Streamlit application
Inside the Streamlit application, the same export still fails with:
choreographer.browsers._errors.BrowserFailedError:
('The browser seemed to close immediately after starting.',
...
'The browser we tried to start is located at /app/.local/share/choreographer/deps/chrome-linux64/chrome.')
The Chrome path used by Choreographer is the same as the one used in the successful manual test.
Additional debug inside the application
I added a direct subprocess.run() call before the Plotly export inside the application:
subprocess.run(
[
"/app/.local/share/choreographer/deps/chrome-linux64/chrome",
"--headless",
"--disable-gpu",
"--dump-dom",
"file:///tmp/chrome-test.html",
],
check=True,
)
Inside the application, this fails with:
chrome_crashpad_handler: --database is required
Try 'chrome_crashpad_handler --help' for more information.
...
subprocess.CalledProcessError:
Command '['/app/.local/share/choreographer/deps/chrome-linux64/chrome', ...]'
died with <Signals.SIGTRAP: 5>.
After trying to add Crashpad-related flags, I still get:
chrome_crashpad_handler: --database is required
[ERROR:third_party/crashpad/crashpad/util/linux/socket.cc:120] recvmsg: Connection reset by peer (104)
...
died with <Signals.SIGTRAP: 5>.
Relevant Dockerfile section
USER root
RUN microdnf install -y \
nss \
nspr \
dbus-libs \
at-spi2-atk \
cups-libs \
libXcomposite \
libXdamage \
libXfixes \
libXrandr \
mesa-libgbm \
libdrm \
libxkbcommon \
pango \
cairo \
alsa-lib \
gtk3 \
xdg-utils \
liberation-fonts \
&& microdnf clean all
USER app
RUN kaleido_get_chrome
USER root
RUN mv /app/.local/share/choreographer/deps/chrome-linux64/chrome \
/app/.local/share/choreographer/deps/chrome-linux64/chrome.real \
&& printf '%s\n' \
'#!/bin/sh' \
'exec /app/.local/share/choreographer/deps/chrome-linux64/chrome.real --no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage "$@"' \
> /app/.local/share/choreographer/deps/chrome-linux64/chrome \
&& chmod +x /app/.local/share/choreographer/deps/chrome-linux64/chrome \
&& chmod +x /app/.local/share/choreographer/deps/chrome-linux64/chrome.real \
&& chown app:app /app/.local/share/choreographer/deps/chrome-linux64/chrome \
&& chown app:app /app/.local/share/choreographer/deps/chrome-linux64/chrome.real
USER app
ENV BROWSER_PATH=/app/.local/share/choreographer/deps/chrome-linux64/chrome
Summary
A minimal Plotly/Kaleido export works when run manually inside the container as user app.
However, the same Chrome binary fails when invoked from the Streamlit application process.
The Chrome binary path is the same in both cases:
/app/.local/share/choreographer/deps/chrome-linux64/chrome
At this point I don't know what to do, any ideas ?
Context
I am trying to make Plotly + Kaleido/Choreographer work inside a Docker image based on AlmaLinux 9.6 minimal, in order to export Plotly figures as PNG images.
Installing Chromium through
dnf/EPEL was problematic because of proxy issues, so I switched to the recommended Kaleido/Choreographer installation command:This installs Chrome at:
Runtime dependencies installed
The following Chrome runtime dependencies were installed:
RUN microdnf install -y \ nss \ nspr \ dbus-libs \ at-spi2-atk \ cups-libs \ libXcomposite \ libXdamage \ libXfixes \ libXrandr \ mesa-libgbm \ libdrm \ libxkbcommon \ pango \ cairo \ alsa-lib \ gtk3 \ xdg-utils \ liberation-fonts \ && microdnf clean allThen this command:
returns nothing.
Sandbox issue identified
Chrome only works with
--no-sandbox.This command works:
This command fails without
--no-sandbox:Error:
Wrapper added around Chrome
Since Choreographer was directly launching:
I renamed the actual Chrome binary to
chrome.realand replacedchromewith a wrapper.Wrapper:
Dockerfile section:
A Chrome smoke test through the wrapper works:
It returns the expected HTML content, although Chrome logs some DBus/OOM warnings, which do not seem to be blocking.
The point where image export works
When opening a shell inside the container as user
app, the following minimal Plotly/Kaleido export works:Output:
So, when run manually in an interactive shell as user
app, PNG export works correctly.Remaining issue inside the Streamlit application
Inside the Streamlit application, the same export still fails with:
The Chrome path used by Choreographer is the same as the one used in the successful manual test.
Additional debug inside the application
I added a direct
subprocess.run()call before the Plotly export inside the application:Inside the application, this fails with:
After trying to add Crashpad-related flags, I still get:
Relevant Dockerfile section
Summary
A minimal Plotly/Kaleido export works when run manually inside the container as user
app.However, the same Chrome binary fails when invoked from the Streamlit application process.
The Chrome binary path is the same in both cases:
At this point I don't know what to do, any ideas ?