Building a Dockerised & Structured Multipage Plotly Dash App

Foong Min Wong
Python in Plain English
4 min readSep 24, 2023

--

There are plenty of online tutorials & blogs on how to build and dockerize a multipage Plotly Dash App, but I would like to share some other things that I learned while making Dash apps.

Multipage URL Handling

When I first started building the multipage Dash app, in the main app file app.py, I used Dash callback and an if-else statement for conditional page content rendering, as shown below:

...
server = Flask(__name__)
app = Dash(__name__, server=server, suppress_callback_exceptions=True, title='Dash Plotly Puzzle ', external_stylesheets=[dbc.themes.FLATLY, dbc.icons.BOOTSTRAP])

content = html.Div(id="page-content", style=CONTENT_STYLE)

def serve_app_layout():
return html.Div([
dcc.Location(id="url"),
navmenu.sidebar(),
content
])

app.layout = serve_app_layout

@callback(Output("page-content", "children"), [Input("url", "pathname")])
def render_page_content(pathname):
if pathname == "/":
return home.layout
elif pathname == "/dropdown":
return dropdown.layout
elif pathname == "/table":
return table.layout
return not_found_404.layout
...

The code works, but as I build more pages, it is daunting to add conditional statements to render additional 10–20 subpages. Dash has good documentation (Multi-Page Apps and URL Support) to simplify the multi-paging and URL-handling process. In each of the page files, add a dash.register_page(__name__) and define layout for each page’s content, for example:

dash.register_page(
__name__,
title='Home',
name='Home',
path='/'
)

layout = html.Div([html.P("This is the content of the home page!")])

When defining the app in app.py, set use_pages to True and replace the app/ page layout conditional page rendering statements to dash.page_container . They look much simpler!

...
server = Flask(__name__)
app = Dash(__name__, server=server, use_pages=True, suppress_callback_exceptions=True, title='Dash Plotly Puzzle ', external_stylesheets=[dbc.themes.FLATLY, dbc.icons.BOOTSTRAP])

def serve_app_layout():
return html.Div([
navmenu.sidebar(),
html.P(dash.page_container, style=CONTENT_STYLE)
])

app.layout = serve_app_layout
...

Design Structure for Large-scale Dash App

I noticed my initial Dash app repository used to be messy; page files, icons, images, .css, and other relevant files were all in one massive folder. Dumping all files in one folder might make things easier to import at one point, but soon the app becomes heavy and complex when the class objects interact with each other. Thus, the design structure of a Dash app needs to be taken into account in large-scale application development.

I discovered a helpful guide for Structuring a large Dash application — best practices to follow. It shares how to organize and design a Dash app directory structure, create .env file to configure environment variables and store sensitive information, etc.

Dash App Directory Structure Overview using dash-app-structure (Github)

Custom 404 Page

The default 404 error page looks something like this in the Dash app:

Default 404 in Dash App

For a better user experience, it is reasonable to customize the 404 page to provide a more user-friendly and helpful experience by explaining that the requested page doesn’t exist and offering alternatives or navigation options.

Custom 404 in Dash App

Containerize Dash App

Using Docker containers has been popular these days to wrap up applications and resolve issues from differences in environments or operating systems. To achieve the consistency from Development to the Production environment, we can dockerize our Dash apps by creating a Dockerfile and run it.

Here’s an example of a Dockerfile:

FROM debian:latest

RUN apt-get update -y && apt upgrade -y && ACCEPT_EULA=Y
RUN apt-get install -y wget \
&& apt-get install -y python3 \
&& apt-get install -y python3-pip

RUN mkdir /dash-plotly-puzzle
WORKDIR /dash-plotly-puzzle
COPY requirements.txt .
RUN pip3 install -r requirements.txt --break-system-packages

COPY ./ ./
CMD [ "gunicorn", "--reload", "--workers=5", "--threads=1", "-b 0.0.0.0:80", "src.app:server"]

Since dash apps are built on top of Flask, we can utilize Gunicorn (Green Unicorn), a WSGI (Web Server Gateway Interface), as an application server to handle HTTP requests and serve our Dash app to users, as stated in the last line of the Dockerfile above.

In terms of deployment flexibility, containerized apps can be deployed to various hosting platform environment, such as on-premise servers, cloud services, or local development machines.

We can deploy a Plotly Dash App without Docker, but we have to do extra steps to setup a web server environment (Gunicorn, Nginx or etc.), manage Python management, and other stuff. To simplify the deployment process, why don’t we encapsulate the Dash app and its dependencies into a container image? 📦

You can check out the Plotly Dash app example in this blog over here!

In Plain English

Thank you for being a part of our community! Before you go:

--

--