How I Built My Python AMA App and Containerized It with Docker

Step-by-step guide on running your own AMA app using Python, Docker, and self-hosted volumes.

By: Irfan Syafi | Date: April 28, 2025 | Tags: Python, Docker, Self-Hosting, Cloudflare Tunnel

Introduction

I recently built a lightweight Ask Me Anything (AMA) application in Python. I decided to containerize it using Docker for better control and portability. Instead of deploying it on cloud, I decided to self-host my own application. In this article, I'll walk you through the containerization process and show how I use Docker to bind ports, pass environment variables, and persist the app's data using volumes.

The AMA App in Python

The AMA application was built to answer submitted questions using Python. Similar to most of the anonymous QnA Application out there. This app will not require any login or registration of the user. The backend handles incoming questions, stores them in a local database, and providing answered question section. The whole application is kept simple as it is just a simple app for me to learn docker and self-hosting which are the main reason.

If you're interested in the source code, I've published it publicly on GitHub:

View the AMA App on GitHub

Why Containerize with Docker?

Docker makes it easier to deploy and run applications across different environments without worrying about dependencies or configuration issues. In my case, it allowed me to run the AMA app on my local Ubuntu machine.

How I Containerized the AMA App

Once I wrote the AMA app in Python, I created a `Dockerfile` to build image and containerize it.

The Dockerfile consist of several Instructions. You can read the full documentation of Docker Instructions here. For a quick look on my Dockerfile and short explaination:

FROM python:3.9-slim

WORKDIR /ama_app

COPY . /ama_app

RUN pip install -r requirements.txt

EXPOSE 2020

CMD ["uvicorn", "main_ama:app", "--host","0.0.0.0", "--port", "2020"]
  • FROM
  • - will pull you a python image from the Docker Hub with their version which are separated by the semicolon.
  • WORKDIR - is the working directory in your image build later. Any instruction given in your Dockerfile will follow this directory.
  • COPY - this instruction is to copy files and directory. It will copy everything (the dot symbol) from my machine working directory into the docker working directory.
  • RUN - this instruction is to run a specific build image command. When building the image, it install all the requirements needed for my application to work properly.
  • EXPOSE - declares the port that the application will listen on. It doesn't actually publish the port to the public. You need to do that when running the container. This actually will help in terms of documentation.
  • CMD - this is the default command when running the container. here I bind the application to my localhost with port 2020

Once your Dockerfile is ready. You can build the application with the build command

docker build -t IMAGE_NAME .

It will take some time depending on the size of the image that you are building. Especially since I have to install all the requirements using pip. You can actually try to use UV to install all the requirements and can see the difference. After that, to run the image into a container, you can run the following command:

docker run -p 2020:2020 --env-file .env -v $(pwd)/database:/ama_python/database python-ama

Here's what this command does:

  • -p 2020:2020 - Maps port 2020 on the host to port 2020 in the container so I can access the app via localhost:2020.
  • --env-file .env - Loads environment variables (like API keys, settings and senstivie information) from a local .env file and passes them into the container.
  • -v $(pwd)/database:/ama_python/database - Mounts the host's `./database` directory into the container. This ensures all the app's data (like sqlite db) is stored persistently outside the container.
  • python-ama - The name of the Docker image built for this app.

This setup allows me to keep app data even if the container is removed or restarted. It also keeps secrets secure by separating them in a `.env` file. Now, when the container is up and running, you can actually access the application through your localhost with the published port.

Cloudflare Tunnel

So, I have an application but it only can be accessed within my localhost and people in the same network with me. How am I going to share my AMA app with friends so that they can ask questions or send anonymous feedback? This is where Cloudflare Tunnel comes in. It's free, but you'll need to own a domain. You can buy one cheaply from providers like Namecheap, Porkbun, GoDaddy, or even directly from Cloudflare. I bought mine from Namecheap.

To use Cloudflare Tunnel, follow these steps:

  1. Set up your domain with Cloudflare:
    First, register your Cloudflare account and add your domain. Once done, go to your domain provider (e.g., Namecheap) and change the nameservers to the ones provided by Cloudflare. This will allow Cloudflare to manage your DNS.
  2. Install cloudflared:
    Open your terminal and run the following commands to install cloudflared:
    sudo apt install cloudflared
    If it doesn't work, you can follow the latest instructions from the official Cloudflare Tunnel docs.
  3. Authenticate cloudflared with Cloudflare:
    Run this command:
    cloudflared login
    This will open a browser asking you to select your domain. After approving it, the credentials will be saved in ~/.cloudflared/.
  4. Create your tunnel:
    Next, you can create a tunnel and give it a name:
    cloudflared tunnel create python-ama
    This will generate a tunnel ID and credentials file in json under ~/.cloudflared/. The credentials file will have similar name to the tunnel ID such as xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
  5. Create a configuration file:
    Create a config file that tells Cloudflare how to route traffic. For example, create a YAML file like this:
    sudo nano /path/to/.cloudflared/python-ama-config.yaml
    Paste the following content and adjust accordingly:
    tunnel: python-ama
          credentials-file: /path/to/.cloudflared/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
    
          ingress:
            - hostname: yourdomain.com
              service: http://localhost:2020
            - service: http_status:404
    The tunnel: python-ama is the name of the cloudflare tunnel that you have create earlier. Replace the hostname with a subdomain of your own domain, and make sure it points to your local port (2020, in this case). The credentials file path should match the JSON file Cloudflare created.
  6. Add a DNS record:
    You can add the record from the Cloudflare Dashboard that have yourdomain.com. In my case, I create a CNAME record in my domain Cloudflare DNS settings. Below are the example of me creating the CNAME record with the Cloudflare tunnel configuration.
  7. Run the tunnel:
    Finally, start your Cloudflare Tunnel by running:
    cloudflared tunnel --config /path/to/.cloudflared/python-ama-config.yaml run python-ama
    Now, anyone with your custom subdomain (e.g., https://yourdomain.com) can access your self-hosted Python AMA application.

That's it! You now have a secure, public URL for your own app without needing to open any ports on your router. This is how I self-hosted my python AMA application and here is my python AMA application: (https://ask-irfan.site)

Contact Me

If you have questions or would like to be in touch whether to improve the project (or want to collaborate), feel free to reach out. I'm also open to learn from others.