Kaleido 1.0.0 errors within Docker container

Kaleido._kaleido_tab.JavascriptError is encountered when running Plotly in a docker container
Stack trace indicates this is emanating from:

File “/usr/local/lib/python3.12/site-packages/kaleido/_kaleido_tab.py”, line 323, in _calc_fig
raise e

Environment:

  • Python 3.12.0
  • Plotly 6.1.1
  • kaleido 1.0.0
  • Acquiring stable Chrome while building the container (this is a MUST, our end-users do not want to access Chrome during run-time- restrictions on Internet use)
  • Verified google-chrome and google-chrome-stable are in the /usr/bin directory of container

The plotting is successful when running the code OUTSIDE the container, so this indicates something(s) was/were overlooked or missing in setting up the container??? Any suggestions for trouble-shooting this issue would be appreciated.


Full stack trace:
File “/METviewer-python/METplotpy/metplotpy/plots/base_plot.py”, line 376, in save_to_file
self.figure.write_image(image_name)
File “/usr/local/lib/python3.12/site-packages/plotly/basedatatypes.py”, line 3911, in write_image
return pio.write_image(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/plotly/io/_kaleido.py”, line 509, in write_image
img_data = to_image(
^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/plotly/io/_kaleido.py”, line 373, in to_image
img_bytes = kaleido.calc_fig_sync(
^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/kaleido/init.py”, line 145, in calc_fig_sync
return _async_thread_run(calc_fig, args=args, kwargs=kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/kaleido/init.py”, line 138, in _async_thread_run
raise res
File “/usr/local/lib/python3.12/site-packages/kaleido/init.py”, line 129, in run
q.put(asyncio.run(func(*args, **kwargs)))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/asyncio/runners.py”, line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/asyncio/runners.py”, line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/asyncio/base_events.py”, line 664, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/kaleido/init.py”, line 55, in calc_fig
return await k.calc_fig(
^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/kaleido/kaleido.py”, line 332, in calc_fig
data = await asyncio.wait_for(
^^^^^^^^^^^^^^^^^^^^^^^
File “/usr/local/lib/python3.12/asyncio/tasks.py”, line 510, in wait_for
return await fut
^^^^^^^^^
File “/usr/local/lib/python3.12/site-packages/kaleido/_kaleido_tab.py”, line 323, in _calc_fig
raise e
kaleido._kaleido_tab.JavascriptError: {‘result’: {‘type’: ‘object’, ‘subtype’: ‘error’, ‘className’: ‘ReferenceError’, ‘description’: ‘ReferenceError: Plotly is not defined\n at Object.render [as plotly] (file:///usr/local/lib/python3.12/site-packages/kaleido/vendor/kaleido_scopes.js:3021:18)\n at :1:48’, ‘objectId’: ‘-5345872899737574680.1.2’}, ‘exceptionDetails’: {‘exceptionId’: 2, ‘text’: ‘Uncaught’, ‘lineNumber’: 3020, ‘columnNumber’: 17, ‘scriptId’: ‘8’, ‘stackTrace’: {‘callFrames’: [{‘functionName’: ‘render’, ‘scriptId’: ‘8’, ‘url’: ‘file:///usr/local/lib/python3.12/site-packages/kaleido/vendor/kaleido_scopes.js’, ‘lineNumber’: 3020, ‘columnNumber’: 17}, {‘functionName’: ‘’, ‘scriptId’: ‘12’, ‘url’: ‘’, ‘lineNumber’: 0, ‘columnNumber’: 47}]}, ‘exception’: {‘type’: ‘object’, ‘subtype’: ‘error’, ‘className’: ‘ReferenceError’, ‘description’: ‘ReferenceError: Plotly is not defined\n at Object.render [as plotly] (file:///usr/local/lib/python3.12/site-packages/kaleido/vendor/kaleido_scopes.js:3021:18)\n at :1:48’, ‘objectId’: ‘-5345872899737574680.1.3’}}}
root@045c5ccf62b5:/tmp#

Hey @mwin I can’t reproduce this. My friend Claude helped me with that.

Dockerfile:

FROM python:3.12-slim

# Set environment variables
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1

# Update and install system dependencies
RUN apt-get update && apt-get install -y \
    python3-pip \
    wget \
    curl \
    gnupg \
    software-properties-common \
    xvfb \
    && rm -rf /var/lib/apt/lists/*


# Install Google Chrome
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
    && apt-get update \
    && apt-get install -y google-chrome-stable \
    && rm -rf /var/lib/apt/lists/*

# Verify Chrome installation
RUN ls -la /usr/bin/google-chrome* && \
    google-chrome --version

# Upgrade pip
RUN python3 -m pip install --upgrade pip

# Install Python packages
RUN pip install plotly==6.1.1 kaleido==1.0.0

# Create working directory
WORKDIR /app

# Copy application files
COPY main.py .

# Create output directory
RUN mkdir -p /app/output

# Run the application
CMD ["python", "main.py"]

docker-compose.yaml

services:
  plotly-app:
    build: .
    container_name: plotly-chart-generator
    volumes:
      - ./output:/app/output
    environment:
      - DISPLAY=:99
    command: >
      bash -c "
        Xvfb :99 -screen 0 1024x768x24 &
        python main.py
      "
    restart: "no"

main.py:

import plotly.graph_objects as go
import plotly.io as pio
import random
import os

def generate_random_line_chart():
    """Generate a random line chart using plotly and save as PNG"""
    
    # Generate random data
    x_values = list(range(1, 21))  # 20 data points
    y_values = [random.randint(10, 100) for _ in range(20)]
    
    # Create the line chart
    fig = go.Figure()
    
    fig.add_trace(go.Scatter(
        x=x_values,
        y=y_values,
        mode='lines+markers',
        name='Random Data',
        line=dict(color='#1f77b4', width=3),
        marker=dict(size=8)
    ))
    
    # Update layout
    fig.update_layout(
        title='Random Line Chart',
        xaxis_title='X Values',
        yaxis_title='Y Values',
        font=dict(size=12),
        width=800,
        height=600,
        template='plotly_white'
    )
    
    # Ensure output directory exists
    os.makedirs('/app/output', exist_ok=True)
    
    # Save as PNG
    output_path = '/app/output/random_line_chart.png'
    fig.write_image(output_path)
    
    print(f"Chart saved successfully to {output_path}")
    print(f"Data points: {len(x_values)}")
    print(f"Y-value range: {min(y_values)} to {max(y_values)}")

if __name__ == "__main__":
    generate_random_line_chart()

Unfortunately, this Dockerfile does not build successfully due to an inability to locate google-chrome-stable.

Works on my end. There is no way we can help you any further without knowing exactly what you are doing.

  • A bit more complicated than the typical plotting paradigm. We have a few ways to generate plots: directly via command line, through use case wrapper scripts, and via Java web app which invokes the Python plotting scripts. The Java web app can be installed on a host, or run in a container.

  • The web app successfully generates plots when installed on a host. The plots are also successfully generated via command line.

  • When the web app is deployed into a container, we observe javascript error observed from _kaleido_tab.py (_calc_fig).

  • Executing the /bin/bash script inside the container and running plotting via command line generates the same javascript error, but provides the stack trace.

Our Dockerfile is a bit more complex, we build on MacOS with the following command:

docker build --platform linux/amd64 -t metviewer .

Dockerfile

ARG MET_BASE_REPO=met-base-metviewer
ARG MET_BASE_TAG=v3.4

FROM dtcenter/${MET_BASE_REPO}:${MET_BASE_TAG}
LABEL org.opencontainers.image.authors="John Halley Gotway <johnhg@ucar.edu>"


#
# This Dockerfile checks out METviewer and its dependencies from GitHub and builds
# the specified branch or tag. Use the develop branches for dependencies by default
# but override with "--build-arg".
#

ARG METPLOTPY_GIT_NAME=feature_525_update_plotly_kaleido
ARG METCALCPY_GIT_NAME=develop
ARG METDATAIO_GIT_NAME=develop

#
# METVIEWER_GIT_NAME is required.
ARG METVIEWER_GIT_NAME=bugfix_kaleido_docker
#
RUN if [ "x${METVIEWER_GIT_NAME}" = "x" ]; then \
      echo "ERROR: METVIEWER_GIT_NAME undefined! Rebuild with \"--build-arg METVIEWER_GIT_NAME={branch, tag, or hash}\""; \
      exit 1; \
    fi 

ENV METVIEWER_GIT_URL=https://github.com/dtcenter/METviewer
ENV CATALINA_HOME=/opt/tomcat

RUN echo "Build Argument METVIEWER_GIT_NAME=${METVIEWER_GIT_NAME}" \
 && echo "Build Argument METPLOTPY_GIT_NAME=${METPLOTPY_GIT_NAME}" \
 && echo "Build Argument METCALCPY_GIT_NAME=${METCALCPY_GIT_NAME}" \
 && echo "Build Argument METDATAIO_GIT_NAME=${METDATAIO_GIT_NAME}"

#
# Set env vars
#
ENV PYTHONPATH="/METviewer-python/METcalcpy/:/METviewer-python/METplotpy/"
ENV METPLOTPY_BASE="/METviewer-python/METplotpy/"

#
# Update the OS, as needed
#
RUN apt update && apt -y upgrade

#
# Clone the METviewer repository
#
RUN echo "Checking out METviewer ${METVIEWER_GIT_NAME} from ${METVIEWER_GIT_URL}"
RUN git clone --branch ${METVIEWER_GIT_NAME} ${METVIEWER_GIT_URL} /METviewer

#
# Configure METviewer
#
RUN echo "Configuring METviewer" \
 && cd /METviewer \
 && cat webapp/metviewer/WEB-INF/classes/build.properties | \
    sed -r 's%db.host=.*%db.host=mysql_mv%g' | \
    sed -r 's%db.user=.*%db.user=root%g' | \
    sed -r 's%db.password=.*%db.password=mvuser%g' | \
    sed -r 's%db.management.system=.*%db.management.system=mysql%g' | \
    sed -r 's%output.dir=.*%output.dir=/opt/tomcat/webapps/metviewer_output/%g' | \
    sed -r 's%webapps.dir=.*%webapps.dir=/opt/tomcat/webapps/metviewer/%g' | \
    sed -r 's%url.output=.*%url.output=http://localhost:8080/metviewer_output/%g' | \
    sed -r 's%python.env=.*%python.env=/usr/local%g' | \
    sed -r 's%metcalcpy.home=.*%metcalcpy.home=/METviewer-python/METcalcpy/%g' | \
    sed -r 's%metplotpy.home=.*%metplotpy.home=/METviewer-python/METplotpy/%g' \
    > build.properties

#
# Run the build script
#
RUN cd /METviewer \
 && internal/scripts/docker/build_metviewer_docker.sh

# Explicitly specify the shell to indicate that using the SHELL
# instruction is a conscious decision: 
# https://docs.docker.com/reference/build-checks/json-args-recommended/
SHELL ["/bin/sh", "-c"]
ENTRYPOINT ${CATALINA_HOME}/bin/startup.sh && /bin/bash
CMD ["true"]


#
# Get Chrome
#
# install wget
RUN apt -f install -y
RUN apt update
RUN apt install -y wget

# Install Chrome
RUN wget -q  https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN apt install -y ./google-chrome-stable_current_amd64.deb

RUN ls -la /usr/bin/google-chrome* && \
      google-chrome --version

# Set the PRE_LOAD_CHROME env variable to True to prevent invoking get_chrome_sync() from METplotpy 
ENV PRE_LOAD_CHROME='False'
RUN echo "INFO: environment PRE_LOAD_CHROME set to ${PRE_LOAD_CHROME} ";


# Ensure correct Plotly and kaleido are installed
# based on requirements.txt file
RUN pip install PLOTLY==6.1.1
RUN pip install kaleido==1.0.0
COPY . .

docker-compose.yml

services:
  db:
      image: mariadb:10.5.8
      container_name: mysql_mv
      user: "mysql:mysql"
      ports:
          - "6603:3306"
      volumes:
          - ${MYSQL_DIR}:/var/lib/mysql

      environment:
          MYSQL_ROOT_PASSWORD: mvuser
      tty: true
  #
  metviewer:
      image: ${METVIEWER_DOCKER_IMAGE}
      container_name: metviewer_1
      ports:
      - "8080:8080"
      volumes:
        - ${METVIEWER_DATA}:/data
        - ${METVIEWER_DIR}/metviewer_output/xml:/opt/tomcat/webapps/metviewer_output/xml
        - ${METVIEWER_DIR}/metviewer_output/plots:/opt/tomcat/webapps/metviewer_output/plots
        - ${METVIEWER_DIR}/metviewer_output/data:/opt/tomcat/webapps/metviewer_output/data
        - ${METVIEWER_DIR}/metviewer_output/scripts:/opt/tomcat/webapps/metviewer_output/scripts
      links:
        - db:mysql_mv
      tty: true

Thank you for your assistance.

I managed to reproduce my error message using your Dockerfile and docker-compose.yml:

because I am using an M2 chip on my Mac, I built this way:

docker build --platform linux/amd64 -t .

And I modified the docker-compose.yml by adding the following:
platform: linux/amd64

Upon inspection of my container log (via Docker Desktop), I can replicate the same error I observe in my plotting (via Java Web app and command line). So perhaps this is an issue with amd64 running on arm???

truncated error message:

ile “/usr/local/lib/python3.12/site-packages/kaleido/kaleido.py”, line 332, in calc_fig

data = await asyncio.wait_for(

^^^^^^^^^^^^^^^^^^^^^^^

File “/usr/local/lib/python3.12/asyncio/tasks.py”, line 520, in wait_for

return await fut

^^^^^^^^^

File “/usr/local/lib/python3.12/site-packages/kaleido/_kaleido_tab.py”, line 323, in _calc_fig

raise e

kaleido._kaleido_tab.JavascriptError: {‘result’: {‘type’: ‘object’, ‘subtype’: ‘error’, ‘className’: ‘ReferenceError’, ‘description’: ‘ReferenceError: Plotly is not defined\n at Object.render [as plotly] (file:///usr/local/lib/python3.12/site-packages/kaleido/vendor/kaleido_scopes.js:3021:18)\n at :1:48’, ‘objectId’: ‘-2948903285713425727.1.2’}, ‘exceptionDetails’: {‘exceptionId’: 2, ‘text’: ‘Uncaught’, ‘lineNumber’: 3020, ‘columnNumber’: 17, ‘scriptId’: ‘8’, 'stac

I actually can’t give you an answer. Let my try to ask around.

@AIMPED thank you for supporting @mwin,

@mwin , this is a question I have to ask my colleagues. We’ll get back from you as soon as we have a good answer.

Thank you for all your assistance. I borrowed an older Mac with an Intel chip and I can successfully generate plots when running in a Docker container. So it appears that the errors emanating from _kaleido_tab.py are linked to running on Macs with M2 chipset. In the meantime, our IT is setting up Docker on linux and I will test in that environment, hopefully the error does not occur in that environment.

We no longer require assistance with this issue. We migrated to Matplotlib. Thanks to all who provided assistance.