Deploy a Dash app to Connect Cloud

Deploy a Dash app to Connect Cloud from GitHub, from Positron or VS Code with Posit Publisher, or with the rsconnect-python CLI.

Skip the guide and deploy the sample app

Deploy the example Dash app to Connect Cloud

In this guide, you deploy a Dash app to Connect Cloud and get a URL you can share. You can deploy from GitHub, from Positron or VS Code, or from the command line. The sample app is a stock tracker that charts prices and trading volume for a set of tickers.

A Dash stock tracker app with a ticker dropdown, price options, a price chart, a volume chart, and a scatter plot matrix

The sample Dash app running locally

The source code is available in the examples-dash repository.

Getting started

Whichever method you choose, you need the following:

  • A Connect Cloud account.

  • A Dash app. This guide uses the sample app, which has a main file (Connect Cloud calls it the primary file) named app.py and a prices.csv data file.

  • A requirements.txt file that lists the Python packages you use, in the same folder as app.py. The sample app needs the following packages:

    requirements.txt
    dash
    dash_bootstrap_components
    plotly
    pandas

Save this file as app.py.

app.py
# -*- coding: utf-8 -*-
import os

import dash
import dash_bootstrap_components as dbc
from dash import dash_table, dcc, html
from dash.dependencies import Input, Output
import pandas as pd
import plotly.express as px

MIN_DATE = pd.Timestamp(2010, 1, 4, 0).date()
MAX_DATE = pd.Timestamp(2018, 11, 7, 0).date()

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# Fetch prices from local CSV using pandas
prices = pd.read_csv(
    os.path.join(os.path.dirname(__file__), "prices.csv"),
    # index_col=0,
    parse_dates=True,
    date_format="%Y-%m-%d",
)

prices["date"] = pd.to_datetime(prices["date"], format="%Y-%m-%d")
tickers = prices["ticker"].unique()

# Dataframe with top 5 volumes for each ticker
max_vol = (
    prices.set_index(["date"]).groupby("ticker")["volume"].nlargest(1).reset_index()
)
min_vol = (
    prices.set_index(["date"]).groupby("ticker")["volume"].nsmallest(1).reset_index()
)
extreme_vol = pd.concat([max_vol, min_vol])
extreme_vol.columns = ["Stock", "Date", "Top and Lowest Volumes"]

# top nav bar
nav = dbc.Navbar(
    children=[
        dbc.Row(
            [
                dbc.Col(dbc.NavbarBrand("Stock Tracker", className="ml-2")),
            ],
            align="center",
            className="g-0",
        ),
    ],
    sticky="top",
)

# left side grouping of selction options
form_card_group = dbc.Card(
    [
        dbc.Row(
            [
                dbc.Label("Choose a Stock Symbol", width=10),
                dbc.Col(
                    dcc.Dropdown(
                        id="stock-ticker-select",
                        options=[
                            {
                                "label": ticker,
                                "value": ticker,
                            }
                            for ticker in tickers
                        ],
                        multi=True,
                        value=[tickers[0]],
                    ),
                ),
            ]
        ),
        dbc.Row(
            [
                dbc.Label("Price", width="auto"),
                dbc.Col(
                    dbc.RadioItems(
                        id="stock-ticker-price",
                        options=[
                            {
                                "label": "Open",
                                "value": "open",
                            },
                            {
                                "label": "High",
                                "value": "high",
                            },
                            {
                                "label": "Low",
                                "value": "low",
                            },
                            {
                                "label": "Close",
                                "value": "close",
                            },
                        ],
                        value="close",
                    ),
                    width=10,
                ),
            ]
        ),
        html.Div(
            [
                dcc.Markdown(
                    """
Selecting data in the **price** graph
will adjust the x-axis date range in the bottom **volume** graph.
"""
                ),
                html.Pre(id="selected-data"),
            ],
        ),
    ],
    body=True,
)

# sidebar
SIDEBAR_STYLE = {
    "float": "left",
    "top": "50px",
    "left": 0,
    "bottom": 0,
    "width": "28rem",
    "padding": "2rem 1rem",
}

sidebar = html.Div(
    form_card_group,
    style=SIDEBAR_STYLE,
)

# price and volume graphs
graphs = [
    dbc.Alert(
        "📊 Hover over the charts to highlight data points and show graph utilities. "
        "All data is historical.",
        color="info",
    ),
    dcc.Graph(id="stock-price-graph", animate=True),
    dcc.Graph(
        id="stock-volume-graph",
        animate=True,
    ),
]

max_table_dash = dash_table.DataTable(
    data=max_vol.to_dict("records"),
    style_as_list_view=True,
    fill_width=False,
    style_table={"float": "left"},
    style_cell={
        "height": "auto",
        # all three widths are needed
        "minWidth": "180px",
        "width": "180px",
        "maxWidth": "180px",
        "whiteSpace": "normal",
        "padding-right": "30px",
        "padding-left": "10px",
        "text-align": "center",
    },
    style_data={"color": "black", "backgroundColor": "white"},
    style_data_conditional=[
        {
            "if": {"row_index": "odd"},
            "backgroundColor": "rgb(220, 220, 220)",
        }
    ],
    style_header={
        "backgroundColor": "rgb(210, 210, 210)",
        "color": "blue",
        "fontWeight": "bold",
    },
)

body_container = dbc.Container(
    [
        html.Div(
            children=[
                dbc.Row(
                    [
                        dbc.Col(
                            sidebar,
                            md=4,
                        ),
                        dbc.Col(
                            graphs,
                            md=8,
                        ),
                    ],
                ),
            ],
            className="m-4",
        ),
        html.Div(
            [
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dcc.Graph(
                                    id="scatter-plot",
                                    style={"float": "left"},
                                ),
                            ]
                        ),
                        dbc.Col([max_table_dash]),
                    ],
                ),
            ],
        ),
    ],
    fluid=True,
)

# main app ui entry
app.layout = html.Div([nav, body_container])


def filter_data_by_date(df, ticker, start_date, end_date):
    """Apply filter to the input dataframe

    Args:
        df: dateframe to filter
        ticker: stock ticker symbol for filter criteria
        start_date: min date threshold
        end_date: max date threshold
    Returns:
        a filtered dataframe by ticker and date range
    """
    if start_date is None:
        start_date = MIN_DATE

    if end_date is None:
        end_date = MAX_DATE

    filtered = df[
        (df["ticker"] == ticker) & (df["date"] >= start_date) & (df["date"] <= end_date)
    ]
    return filtered


def volume_figure_layout(selected_tickers, xaxis_range=None):
    """Add layout specific to x-axis

    Args:
        selected_tickers: stock tickers for title
        xaxis_range: `dict` with layout.xaxis.range config
    Returns:
        a layout dict
    """
    layout = dict(xaxis={}, yaxis={})
    layout["title"] = "Trading Volume (%s)" % (" & ").join(selected_tickers)
    layout["yaxis"] = {"autorange": True}
    layout["yaxis"]["title"] = "Volume"
    layout["xaxis"]["title"] = "Trading Volume by Date"

    if xaxis_range:
        layout["xaxis"]["range"] = xaxis_range
        layout["xaxis"]["autorange"] = True

    return layout


@app.callback(
    Output("stock-price-graph", "figure"),
    [
        Input("stock-ticker-select", "value"),
        Input("stock-ticker-price", "value"),
    ],
)
def update_price_figure(tickers, price):
    """Create a plot of stock prices

    Args:
        tickers: ticker symbols from the dropdown select
        price: the radio button price selection
    Returns:
        a graph `figure` dict containing the specificed
        price data points per stock
    """

    return {
        "data": [
            {
                "x": [date for date in prices.loc[(prices.ticker == stock)]["date"]],
                "y": [p for p in prices.loc[(prices.ticker == stock)][price]],
                "type": "scatter",
                "mode": "lines",
                "name": stock,
            }
            for stock in tickers
        ],
        "layout": {
            "title": "Stock Price - (%s)" % " & ".join(tickers),
            "xaxis": {"title": "Date"},
            "yaxis": {"title": "Price"},
        },
    }


@app.callback(
    Output("stock-volume-graph", "figure"),
    [
        Input("stock-ticker-select", "value"),
        Input("stock-price-graph", "relayoutData"),
    ],
)
def update_volume_figure(selected_tickers, relayoutData):
    """Create a plot of stock volume

    Args:
        selected_tickers: ticker symbols from the dropdown select
        relayoutData: data emitted from a `selection` on the price graph
    Returns:
        a graph `figure` dict containing the specificed
        volume data points per stock within the relayoutData
        date range.
    """

    data = []
    from_date = None
    to_date = None

    if relayoutData:
        from_date = relayoutData.get("xaxis.range[0]", None)
        to_date = relayoutData.get("xaxis.range[1]", None)

        if from_date and to_date:
            from_date = pd.Timestamp(from_date)
            to_date = pd.Timestamp(to_date)

            for stock in selected_tickers:
                filtered = filter_data_by_date(prices, stock, from_date, to_date)
                data.append(
                    {
                        "x": filtered["date"],
                        "y": filtered["volume"],
                        "type": "bar",
                        "name": stock,
                    }
                )

            xaxis_range = [from_date, to_date]

            return {
                "data": data,
                "layout": volume_figure_layout(selected_tickers, xaxis_range),
            }

        else:
            data = [
                {
                    "x": [item for item in prices[(prices.ticker == stock)]["date"]],
                    "y": [item for item in prices[(prices.ticker == stock)]["volume"]],
                    "type": "bar",
                    "name": stock,
                }
                for stock in selected_tickers
            ]

            # default dates
            xaxis_range = [MIN_DATE, MAX_DATE]

            return {
                "data": data,
                "layout": volume_figure_layout(selected_tickers, xaxis_range),
            }

    return {"data": data, "layout": volume_figure_layout(selected_tickers)}


@app.callback(
    Output("scatter-plot", "figure"),
    [
        Input("stock-ticker-select", "value"),
        Input("stock-ticker-price", "value"),
    ],
)
def update_scatter_plot(all_tickers, price):
    dfs = []
    if len(list(all_tickers)) < 2:
        for stock in ["AAPL", "AMZN", "FB", "GOOG", "INTC", "MSFT"]:
            temp = prices.loc[(prices["ticker"] == stock)]
            temp = temp.loc[temp.date >= "2012-05-18"]
            dfs.append(temp)

        final = pd.concat(dfs, ignore_index=True)
        final["daily ret"] = (final["close"] - final["open"]) * 100 / final["open"]
        final = final[["date", "ticker", "daily ret"]]

        unique_dates = pd.unique(temp["date"])
        date_final = pd.DataFrame({"Date": unique_dates})

        for stock in ["AAPL", "AMZN", "FB", "GOOG", "INTC", "MSFT"]:
            col_name = stock
            date_final[col_name] = final.loc[final["ticker"] == stock][
                "daily ret"
            ].values
        ret_list = date_final.columns[1:]
        fig = px.scatter_matrix(date_final, dimensions=ret_list)
        fig.update_traces(diagonal_visible=False)
        fig.update_layout(title={"text": "Return Price Scatter Plot"})
    else:

        for stock in all_tickers:
            temp = prices.loc[(prices["ticker"] == stock)]
            temp = temp.loc[temp.date >= "2012-05-18"]
            dfs.append(temp)

        final = pd.concat(dfs, ignore_index=True)
        final["daily ret"] = (final["close"] - final["open"]) * 100 / final["open"]
        final = final[["date", "ticker", "daily ret"]]

        unique_dates = pd.unique(temp["date"])
        date_final = pd.DataFrame({"Date": unique_dates})

        for stock in all_tickers:
            col_name = stock
            date_final[col_name] = final.loc[final["ticker"] == stock][
                "daily ret"
            ].values

        ret_list = date_final.columns[1:]
        fig = px.scatter_matrix(date_final, dimensions=ret_list)
        fig.update_traces(diagonal_visible=False)
        fig.update_layout(title={"text": "Return Price Scatter Plot"})

    return fig


if __name__ == "__main__":
    app.run_server()

Get prices.csv from the sample repository.

To preview the app before you deploy, install the dependencies and run it from the folder that contains app.py:

Terminal
pip install -r requirements.txt
python app.py

Choose a deployment method

Method When to use it Jump to
GitHub Your code is on GitHub and you want it to update when you push (public repositories only on free accounts) Deploy from GitHub
Posit Publisher You work in Positron or VS Code and do not use GitHub Deploy from Positron or VS Code
rsconnect-python You want to deploy from a terminal or automate deployments Deploy from the command line

For more on how these methods differ, see Publishing.

Deploy from GitHub

Use this method when your app’s code is in a GitHub repository and you want changes you push to publish automatically. On a free account, your repository must be public, so anyone can see your code.

  1. Commit app.py and requirements.txt, then push your app to a GitHub repository.
  2. Sign in to Connect Cloud.
  3. Click Publish at the top of your Home page. If this is your first time publishing, Connect Cloud prompts you to install the GitHub App, which lets Connect Cloud read your repository.
  4. Select Dash.
  5. Select your repository.
  6. Check that Branch is the branch that has your content, usually main.
  7. Select app.py as the Primary file.
  8. Leave Automatically publish on push on if you want Connect Cloud to update what you published each time you push to this branch. Turn it off to update only when you choose.
  9. Click Publish.
  • Result: Connect Cloud shows a log of the install steps while it deploys. When it finishes, your app is live at an address like https://[content-id].share.connect.posit.cloud, where [content-id] is a unique identifier that Connect Cloud picks. You can set a custom name to change the URL.
  • Update or redeploy: Commit and push to the connected branch and Connect Cloud republishes automatically. If you turned off automatic publishing, click Republish on the content’s card on your Home page. See Republishing.

For all options, see Publish from GitHub.

Deploy from Positron or VS Code

Use this method when you work in Positron or VS Code and want to publish your app directly. Positron includes the Posit Publisher extension. In VS Code, install it from the marketplace first.

  1. Open your project folder in Positron, or in VS Code with Posit Publisher installed.
  2. Click the Posit Publisher icon in the Activity Bar.
  3. Next to CREDENTIALS, click + and select Posit Connect Cloud. A credential is a saved connection to your Connect Cloud account. Your browser opens.
  4. In your browser, check that the Authorize Access code matches the code in Positron or VS Code. Then click Continue and Authorize.
  5. Return to Positron or VS Code and accept or change the name for this credential. Do this once.
  6. Click the + next to Deployments. If Posit Publisher asks which file to publish, select app.py.
  7. Enter a title for your content, then select the credential you just added.
  8. Under PROJECT FILES, check that the list includes app.py, prices.csv, and requirements.txt. Connect Cloud uploads only the files in this list.
  9. Click Deploy Your Project.
  • Result: A success message appears in the lower right of Positron or VS Code. Click View Content in the extension to open the app on Connect Cloud. Its address has the form https://[content-id].share.connect.posit.cloud, where [content-id] is unique to your app.
  • Update or redeploy: Save your changes, open Posit Publisher, select the previous deployment, and click Deploy Your Project again. Keep the .posit folder in your project. It remembers which Connect Cloud content this project publishes to, so the next deploy updates the same content instead of creating a new one.

For the configuration file, multiple deployments per project, and troubleshooting, see Publish from your IDE.

Deploy from the command line

Use this method to deploy from a terminal or to automate deployments, for example, from a script. It requires rsconnect-python version 1.31.0 or higher.

  1. Install the rsconnect-python command-line tool.

    Terminal
    pip install rsconnect-python
  2. Add your Connect Cloud account. Replace <YOUR_ACCOUNT_HERE> with your account name from your account page. Your browser opens for you to authorize access.

    Terminal
    rsconnect add --connect-cloud \
      --account "<YOUR_ACCOUNT_HERE>" \
      --name cloud
  3. Deploy the folder that contains app.py and requirements.txt.

    Terminal
    rsconnect deploy dash . --name cloud
  • Result: The command prints the URL of your content. Open the URL to see the content’s page on Connect Cloud. Add --quiet to print only the URL.
  • Update or redeploy: Run the same rsconnect deploy command again. The directory remembers where you deployed, so you can omit --name.

For noninteractive credentials, examples for automated builds, and other options, see Publish from your console or terminal.

After you deploy

Common things to set up next. They work the same however you deployed your app:

  • Passwords and API keys: Store them as secret variables, not in your code. Add them under Variables in content settings, or during publishing. Read them with os.getenv("NAME").
  • Access and sharing: Control who can view your app in Sharing.
  • Scheduling: On paid plans, schedule your app to republish at set times, for example, to refresh data each day.
  • Custom name and domain: Change the app’s URL with a custom name or a custom domain.
  • Upload size limit: The upload size limit applies when you upload your project files from your editor or the command line.

Troubleshooting

  • Missing package error: Add the package to requirements.txt and republish. Make sure the file is in the same directory as app.py.
  • Missing data file: Commit the file to the repository, or include it under PROJECT FILES in Posit Publisher. Connect Cloud can use only the files you deploy with the app.
  • Repository not listed: Confirm that the GitHub App has access to it.
  • The deploy command stops before uploading: The folder you are deploying needs a requirements.txt file. See Create a requirements.txt file.

If your deployment still fails, read the log that Connect Cloud shows while it deploys and look for the first error. You can also try Error Assist, which gives guidance based on your log output.

Next steps