In this article, we will set up a Flask application using uv, enhance its appearance with Bootstrap, and finalize the project by containerizing it with Docker.
Intro#
In this article, we’ll build a Flask application using uv as the dependency manager, render an HTML template styled with Bootstrap for a modern look, and set up Docker configuration to containerize the app
Setup#
In order to prepare the python environment for this work lets use uv and install flask
uv init
uv venv
uv pip install flask
source .venv/bin/activate
Flask#
Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to complex applications.
The app.py code implements a simple web application using Flask framework. The app allows users to trigger the execution of a Python script (your_script.py) through a web interface and displays the script’s output on the page as default renders the index.html template.
from flask import Flask, render_template, request
import subprocess
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
output = None
if request.method == 'POST':
# Replace 'your_script.py' with your actual Python script
result = subprocess.run(['python3', 'your_script.py'], capture_output=True, text=True)
output = result.stdout or result.stderr
return render_template('index.html', output=output)
if __name__ == '__main__':
app.run(debug=True)
And create a sample template:
mkdir templates
echo "Hello World" > templates/index.html
In order to test one can execute:
uv run app.py
And access http://127.0.0.1:5000
Now lets enhance a bit the interface with bootstrap.
Bootstrap#
Bootstrap is a powerful, feature-packed frontend toolkit
We are going to change our previous index.html with
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask Example App</title>
<!-- Bootstrap 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<form method="post" class="text-center">
<div class="card shadow p-4">
<div class="card-body">
<h1 class="card-title mb-4 text-center">Flask App</h1>
</div>
<div class="hstack">
<div class="card m-2" style="width: 18rem; height: 30rem;">
<svg xmlns='http://www.w3.org/2000/svg' width='100%' viewBox='0 0 200 200'><rect fill='#2169EE' width='200' height='200'/><defs><linearGradient id='a' gradientUnits='userSpaceOnUse' x1='100' y1='33' x2='100' y2='-3'><stop offset='0' stop-color='#000' stop-opacity='0'/><stop offset='1' stop-color='#000' stop-opacity='1'/></linearGradient><linearGradient id='b' gradientUnits='userSpaceOnUse' x1='100' y1='135' x2='100' y2='97'><stop offset='0' stop-color='#000' stop-opacity='0'/><stop offset='1' stop-color='#000' stop-opacity='1'/></linearGradient></defs><g fill='#0058d9' fill-opacity='0.6'><rect x='100' width='100' height='100'/><rect y='100' width='100' height='100'/></g><g fill-opacity='0.5'><polygon fill='url(#a)' points='100 30 0 0 200 0'/><polygon fill='url(#b)' points='100 100 0 130 0 100 200 100 200 130'/></g></svg>
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Description of the execution.</p>
<button type="submit" class="btn btn-primary btn-lg">Run Script</button>
</div>
</div>
<div class="card m-2" style="width: 18rem; height: 30rem;">
<svg xmlns='http://www.w3.org/2000/svg' width='100%' viewBox='0 0 200 200'><rect fill='#FFFFFF' width='200' height='200'/><g fill-opacity='1'><polygon fill='#003ca9' points='100 57.1 64 93.1 71.5 100.6 100 72.1'/><polygon fill='#1e49ba' points='100 57.1 100 72.1 128.6 100.6 136.1 93.1'/><polygon fill='#003ca9' points='100 163.2 100 178.2 170.7 107.5 170.8 92.4'/><polygon fill='#1e49ba' points='100 163.2 29.2 92.5 29.2 107.5 100 178.2'/><path fill='#3A58CC' d='M100 21.8L29.2 92.5l70.7 70.7l70.7-70.7L100 21.8z M100 127.9L64.6 92.5L100 57.1l35.4 35.4L100 127.9z'/><polygon fill='#0055a2' points='0 157.1 0 172.1 28.6 200.6 36.1 193.1'/><polygon fill='#0067b7' points='70.7 200 70.8 192.4 63.2 200'/><polygon fill='#257ACC' points='27.8 200 63.2 200 70.7 192.5 0 121.8 0 157.2 35.3 192.5'/><polygon fill='#0067b7' points='200 157.1 164 193.1 171.5 200.6 200 172.1'/><polygon fill='#0055a2' points='136.7 200 129.2 192.5 129.2 200'/><polygon fill='#257ACC' points='172.1 200 164.6 192.5 200 157.1 200 157.2 200 121.8 200 121.8 129.2 192.5 136.7 200'/><polygon fill='#0055a2' points='129.2 0 129.2 7.5 200 78.2 200 63.2 136.7 0'/><polygon fill='#257ACC' points='200 27.8 200 27.9 172.1 0 136.7 0 200 63.2 200 63.2'/><polygon fill='#0067b7' points='63.2 0 0 63.2 0 78.2 70.7 7.5 70.7 0'/><polygon fill='#257ACC' points='0 63.2 63.2 0 27.8 0 0 27.8'/></g></svg>
<div class="card-body">
<h5 class="card-title">Card title 2</h5>
<p class="card-text">Description of the execution 2.</p>
<button type="submit" class="btn btn-primary btn-lg">Run Script</button>
</div>
</div>
</div>
{% if output %}
<div class="alert alert-info mt-4" role="alert">
<h5>Script Output:</h5>
<pre>{{ output }}</pre>
</div>
{% endif %}
</div> <!-- card-shadow -->
</form>
</div>
</div>
</div>
<!-- Bootstrap JS (optional, for advanced components) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
To use Bootstrap, you need to include the required library. There are several ways to do this, but in this case, we’re using a CDN reference. In the previous example, we included two cards. For more examples and reusable components, visit the following URL, which contains a variety of useful snippets.
Docker#
In order to have the application running in a docker container, let’s start by creating our Dockerfile
FROM python:3.12-slim-bookworm
COPY --from=ghcr.io/astral-sh/uv:0.7.13 /uv /uvx /bin/
EXPOSE 5000
# Copy the project into the image
ADD . /app
# Sync the project into a new environment, asserting the lockfile is up to date
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"
# Reset the entrypoint, don't invoke `uv`
ENTRYPOINT []
# Run the Flask application by default
CMD ["flask", "run","--host=0.0.0.0"]
This Dockerfile builds a lightweight Python environment for running a Flask web application, using uv as a dependency manager. Let’s break it down step by step:
- Starts from a slimmed-down official Python 3.12 image based on Debian Bookworm.
- Copies the
uvbinary tools from the official uv image into/binof this image. - Declares that the container will listen on port
5000 - Adds the entire current project directory
.into the container at/app. - Sets the working directory to
/appfor all following commands - Installs dependencies using the lockfile
uv.lock - Adds the virtual environment’s
binfolder to the systemPATH - Resets the entrypoint. Prevents
uvor any default entrypoint from running when the container starts. - Default the start flask command to be accessible outside the container
Next step we are going to create a docker-compose.yaml with the following content
---
services:
flask-app:
ports:
- "5000:5000"
build: .
# ...
develop:
# Create a `watch` configuration to update the app
#
watch:
# Sync the working directory with the `/app` directory in the container
- action: sync
path: .
target: /app
# Exclude the project virtual environment
ignore:
- .venv/
# Rebuild the image on changes to the `pyproject.toml`
- action: rebuild
path: ./pyproject.toml
You can start the container by executing docker-compose up -d and access the application http://127.0.0.1:5000
Repo#
You can use the following repository that has this code https://github.com/rramos/uv-flask
Conclusion#
In this article, we walked through the process of setting up a Flask application with uv as the dependency manager. We then updated the index template using Bootstrap for a cleaner, more modern look. Finally, we created a Dockerfile and a docker-compose configuration to containerize the application.
The shared repository can serve as a starter skeleton for quick Python experiments or as a ready-to-use Docker setup to easily share your project with others.