Building up on an excellent AI Makerspace video about Langgraph, I've decided to follow their build-ship-share
mentality and to put the whole thing in a deployed UI over the web.
I've been hearing a lot about Streamlit and how you can easily spin up little POCs (and sometimes bigger things with it): I was not disappointed. However, if you need to deploy your app on a Ubuntu server, the docs are not very talkative about it and all you get from the official website is some links to various unstructured answers in their forum...
So I've taken a shot at it, so you won't waste much time as I did, the example repo is linked here. The application UI in itself is not very interesting, as I was just toying around with this part of Streamlit docs; I want to emphasize in this article on deployment and not on the logic of this app.
(Note: all the listed steps, whether locally or on the remote, were executed on a Ubuntu 24 system)
run a streamlit app on Ubuntu 24: initial setup
My app (see link above) was as agentic-rag-demo
, so naturally I've created an agentic-rag-demo
folder on my remote server home folder.
copy the repo from my machine to the remote server
From the beginning, my goal was to deploy the app automatically as I make changes to it; however, I found it way more practical to deploy it manually the first time, so that I can rest assured everything works fine before letting the CI/CD take care of the updates, e.g.:
scp -r <repo> <user>@<remote-server-ip>:/home/<remote-user>/agentic-rag-demo
(replace <repo>
, <user>
, <remote-server-ip>
, and <remote-user>
with the appropriate values...
The above command copies the repo I had locally into the remote server using scp
.
nginx configuration
install system deps
- back to your remote server from there on:
sudo apt update && sudo apt upgrade -y
sudo apt install nginx
-
sudo apt install apache2-utils
(this package is useful for basic auth, as you will see later)
folders and permissions
-
sudo mkdir -p /var/www/<domain>
(replace<domain>
with your domain or subdomain) sudo chown -R www-data:www-data /var/www/<domain>
sudo chmod -R 755 /var/www/<domain>
a note on basic auth in my use-case
As my app uses an OpenAI API key that is stored in the Python env (and I don't know yet how to implement a login functionality in Streamlit), I've opted for simplicity: put the application behind Basic auth, so that the whole Internet does not suck up my OpenAI credits 😁
-
sudo htpasswd -c /etc/nginx/.htpasswd demo
(demo
is the username, you will be prompted to enter a password)
The command above creates a user/password pair that I will be able to use later in my nginx conf to restrict access to my website.
now onto the website conf
sudo nano /etc/nginx/sites-available/<domain>.conf
- write this content into the file =>
server {
listen 80;
listen [::]:80;
root /var/www/<domain>;
index index.html;
server_name <domain>;
location / {
auth_basic "demo app request password @ <email>";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://0.0.0.0:8501;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
This configuration allows to access your Streamlit app running locally on port 8501 via Nginx, the http_upgrade
conf here is for allowing websockets, as Streamlit relies heavily on them.
-
sudo ln -s /etc/nginx/sites-available/<domain>.conf /etc/nginx/sites-enabled/
(enabling the site) -
sudo nginx -t
(testing the syntax) sudo systemctl reload nginx
wait... what about the domain? 🌎
You'll need to configure an A record in your DNS settings to point to your server's IP address for your domain or subdomain. There are plenty of resources on the Internet to help you just do that.
Before moving on, just make sure DNS is propagated. You can use a free DNS checker tool for this.
the Python part
You'll need to:
- install your Python dependencies
- start the app
For the app to be served... makes sense right?
cd agentic-rag-demo
pip install -r requirements.txt
-
streamlit run agentic_rag.py
(of course your script may have a different name) - at this point, you should be able to see the dummy content when you navigate to your domain or subdomain on plain HTTP
TLS
This one is easy, just go to the official certbot website and follow the instructions, they are crystal clear.
Running the Streamlit app as a Ubuntu service
Now we want to create a service for the app to run in the background, this helps us:
- manage crashes
- start the app on boot
- release the terminal when we start the app from our CI/CD pipeline
- provide us with an easy way of stopping the app in the same CI/CD pipeline before updating it
One particularity here is that we will create a user systemd
service, which does not require sudo
privileges, this is way easier to handle in CI/CD pipelines, as we will not have to connect as root
to stop the app, update it, and relaunch it. Here is how to do it:
-
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/agentic-rag.service
- write the following content:
[Unit]
Description=a service for the agentic rag demo
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 -m streamlit run /home/<remote-user>/agentic-rag-demo/agentic_rag.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.target
... don't forget to replace the placeholders with the appropriate values.
- now we need our service to be stoppable/start-able from our CI/CD pipeline with our regular user so, for the sake of testing, let's do =>
systemctl --user daemon-reload
systemctl --user enable --now agentic-rag.service
systemctl --user start agentic-rag.service
systemctl --user status agentic-rag.service
- to stop the service =>
systemctl --user stop agentic-rag.service
, as you can see we never neededsudo
! - we are now ready to let the CI/CD pipeline do its magic 🚀
the GitHub Actions workflow
To be able to use this workflow, you will need to fill in a few repository secrets.
Things are always cooler when deployed automatically, here is my commented pipeline:
name: cicd
on:
push:
branches: [master]
jobs:
deploy-app:
runs-on: ubuntu-latest
steps:
- name: actions/checkout@v4
uses: actions/checkout@v4
- name: start-app
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
key: ${{ secrets.SSH_PRIV_KEY }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USERNAME }}
script_stop: true
script: |
cd ~/agentic-rag-demo
# we stop the app before copying the files to update it
systemctl --user stop agentic-rag.service
- name: copy-files
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SSH_HOST }}
key: ${{ secrets.SSH_PRIV_KEY }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USERNAME }}
source: "./*"
target: /home/${{ secrets.SSH_USERNAME }}/agentic-rag-demo
- name: deploy-app
uses: appleboy/ssh-action@v1.0.3
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
host: ${{ secrets.SSH_HOST }}
key: ${{ secrets.SSH_PRIV_KEY }}
port: ${{ secrets.SSH_PORT }}
username: ${{ secrets.SSH_USERNAME }}
script_stop: true
envs: OPENAI_API_KEY
script: |
cd ~/agentic-rag-demo
# this one below I'm not proud of, I was just lazy to use a virtual env
python3 -m pip install --break-system-packages -r requirements.txt
cp .env.example .env
> .env
echo "OPENAI_API_KEY=${OPENAI_API_KEY}" >> .env
mv .streamlit/config.remote.toml .streamlit/config.toml
systemctl --user start agentic-rag.service
wrapping it up
Now you know how to self-host a Streamlit app, spin applications up and keep them coming!
Top comments (0)