Self Hosted Website on a Raspberry Pi (Part 2)

Photo by Ian Taylor on Unsplash

Self Hosted Website on a Raspberry Pi (Part 2)

Flask App with Docker and Portainer

This is a three part tutorial that cites relevant tutorial guides that I found online that help me eventually achieve my final goal of self-hosting my personal website.

To add more content to my old personal website, I created more routes to different pages. However, considering that the future of this personal website will be where I experiment with different frameworks, not every side project I will be doing can be integrated with Flask. So I decided to take a leap of faith to experiment for Docker, which after countless hours of tutorials I am starting to realise the value of it.

Why Docker?

dockervsvm.png My old personal website was built directly into the VPS, which are essentially virtual machines. When I install packages directly into the OS such as tensorflows/sklearn/xgboost, they may run into conflicts in the version of packages that they are dependent on. Spinning up another VPS or Raspberry Pi is definitely too overkill, so one may consider isolating package installs through virtual environments (venv).

However, that only works if we are working with only Python. What if we want to set up a MYSQL or redis server to work with our Flask app? What if we need that the same MYSQL database but a different version with another programming language? That’s when things becomes complicated. Spinning up more virtual machine on a virtual machine is too expensive, and communication between these virtual machine is even harder.

That’s when Docker comes into play, and compartmentalise individual application based on their dependencies, but allow them to still “share” libraries with other applications without duplicating them. All the application will share the same host OS, without the extra layer of guest OS that is present in virtual machine set up.

Installing Docker (and Portainer - optional)

Now it’s time to install Docker! There is a simple script for that, just run:

$ curl -sSL | sh
$ sudo pip install docker-compose
$ docker ps

We also use docker-compose for multi-container Docker image, which requires python which we install previously. Run docker ps to check if docker is successfully installed. It should show you CONTAINER ID, IMAGE, COMMAND as output.

[Optional] However, I like having a GUI to have a better view of what is going on, which is why we are going to install Portainer, a docker image that help us manage our docker images 🙂

$ sudo docker run -d -p 9443:9443 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:linux-arm

To break down the command, -p 9443:9443 expose the portainer web client on the port 9443, and --restart=always make sures that portainer is always running even if something went round. The last part we are installing the community edition of portainer for raspberry pi which is an ARM chip. Here you should go to Port Forwarding settings in your router that we mention in Part 1 to open up 9443 if you choose to install Portainer.

Once portainer is running on docker, you can access the web UI through localhost:9443 or public IP address:9443. You would arrive here, and create an admin account accordingly

portainer.png After setting up the admin account you should reach this website. At the home page, there are a few containers that are already running for me, but you should have no containers that are running if it’s your first setup.

Creating Flask App

First thing first, we need to update and install all the dependencies we need in the RPi, by running the commands below:

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install python3-pip
$ sudo apt-get install python3-dev
$ sudo apt-get install python3-setuptools
$ sudo apt-get install python3-venv
$ sudo apt-get install build-essential libssl-dev libffi-dev

With that we can start by creating our barebone Flask App in any directories you want in RPi with the dependencies printed out in requirements.txt

$ mkdir testflask
$ cd testflask
$ python -m venv venv
$ source ./venv/bin/activate
(venv)$ pip install wheels
(venv)$ pip install flask
(venv)$ pip freeze > requirements.txt
(venv)$ sudo nano
from flask import Flask
app = Flask(__name__)

def index():
    return "<h1>Hello World</h1>"

if __name__ == "__main__":'', port='5000')

After creating the file, we can run it to ensure it works.

(venv)$ python
 * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on all addresses (
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on
 * Running on (Press CTRL+C to quit)
(venv)$ deactivate

With this, we know our flask app is functioning, but this is only for development purposes. Now let’s deactivate and add some files to Dockerise our flask app. Create two files Dockerfile and docker-compose.yaml with the code below.


FROM python:3.11.0a6-alpine3.15
COPY requirements.txt /code
RUN pip install -r requirements.txt --no-cache-dir
COPY . /code
CMD python


        build: .
            - "5000:5000"
            - .:/code

Docker compose uses the Dockerfile if you add the build command to your project’s docker-compose.yml. Your Docker workflow should be to build a suitable Dockerfile for each image you wish to create, then use compose to assemble the images using the build command. The docker-compose also determines which port is exposed, so if you are already using port 5000 for something, do change it to any other open port. You can also see that as long your application can run on Docker, even if it is a React App with Java Backend, you can still set it up this way to be hosted.

Now, you can access your website if you go to :5000. However, this is not the end, we will still need to link it up with your domain name and setup basic security for your server and website, which will be explained in the next part.


Due to firewall settings which will be set up in the next part of the tutorial, you may need to pip install with trusted tags as seen from below if you are creating another flask app to be hosted

pip install --trusted-host=[]( --trusted-host=[]( flask

Did you find this article valuable?

Support Fan Ting Wei by becoming a sponsor. Any amount is appreciated!