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.
- Part 1 - RPi Setup
- Part 2 - Flask App with Docker and Portainer (here)
- Part 3 - Domain Name Management with Cloudflare and Nginx Proxy Manager
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?
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 https://get.docker.com | 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
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 app.py
#app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "<h1>Hello World</h1>"
if __name__ == "__main__":
app.run(host='0.0.0.0', port='5000')
After creating the file, we can run it to ensure it works.
(venv)$ python app.py
* 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 (0.0.0.0)
WARNING: This is a development server. Do not use it in a production deployment.
* Running on http://127.0.0.1:5001
* Running on http://192.168.10.51:5001 (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.
Dockerfile
FROM python:3.11.0a6-alpine3.15
WORKDIR /code
COPY requirements.txt /code
RUN pip install -r requirements.txt --no-cache-dir
COPY . /code
CMD python app.py
docker-compose.yml
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/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.
P.S.
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=[pypi.org](http://pypi.org/) --trusted-host=[files.pythonhosted.org](http://files.pythonhosted.org/) flask