We are going to create a very simple web application with the following features:
First, let's set up our project. We need one Python file for our Flask logic and a folder to hold our HTML templates.
Install Flask if you haven't already:
python -m venv .venv
# on macOS/Linux
source .venv/bin/activate
# On Windows use `.venv\Scripts\activate`
pip install Flask
Then, create the following folder and file structure:
todo-app/
├── app.py
└── templates/
└── index.html
Let's start with a minimal "Hello World" Flask application in our app.py file.
This code imports Flask, creates an application instance, defines a single route /, and makes the app runnable.
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, To-Do App!"
if __name__ == '__main__':
app.run(debug=True)
For this simple app, we won't use a database. We'll store our to-do items in a simple Python list.
This list will act as our in-memory "database". Note that it will reset every time you restart the server.
# app.py
from flask import Flask
app = Flask(__name__)
# Our in-memory "database"
todos = ["Learn Flask Basics", "Build a To-Do App"]
@app.route('/')
def index():
return "Hello, To-Do App!"
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
Before we create our template, let's talk about HTML. HTML stands for HyperText Markup Language. It's the standard language for creating web pages.
It describes the structure of a web page using elements, which are represented by "tags". Think of it as the skeleton of a website.
<p> and </p>.Here are some basic tags we will use in our to-do app:
<h1>: A main heading.<ul>: An "unordered list" (a bulleted list).<li>: A "list item" that goes inside a <ul>.Together, they create a structured list on the page.
<!-- Example of a simple list -->
<h1>My Shopping List</h1>
<ul>
<li>Milk</li>
<li>Bread</li>
<li>Cheese</li>
</ul>
To get input from a user, we use the <form> tag. Key parts of a form are:
<form>: The container.
action: The URL where the data is sent.method: How the data is sent (e.g., `post`).<input>: A field for user input. Its `name` attribute is crucial for the server to identify the data.<button>: Submits the form.<!-- Example of a simple form -->
<form action="/submit-data" method="post">
<label>Your Name:</label>
<input type="text" name="username">
<button type="submit">Submit</button>
</form>
Flask uses a powerful tool called Jinja2. It's a "templating engine."
A templating engine lets us embed Python-like code directly into our HTML files. This is how we make our web pages dynamic. Instead of writing static HTML, we can:
This allows us to use one HTML file as a "template" for many different pieces of data.
Jinja2 has two primary delimiters that you'll see in your HTML templates:
{{ ... }} for Expressions:
This is used to print a variable or the result of an expression to the template. Think of it as a placeholder that gets filled in.
{% ... %} for Statements:
This is used for control flow, like for loops or if statements. It controls the structure of the template.
<!-- Jinja2 Syntax Examples -->
<!-- Prints the value of the 'name' variable -->
<h1>Hello, {{ name }}!</h1>
<!-- A for loop to create list items -->
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
Instead of returning plain text, let's render an HTML file. We use Flask's render_template function for this.
We'll also pass our todos list to the template so we can display it on the webpage.
# app.py
from flask import Flask, render_template
app = Flask(__name__)
todos = ["Learn Flask Basics", "Build a To-Do App"]
@app.route('/')
def index():
# Pass the 'todos' list to the template
return render_template('index.html', todos=todos)
# ... (if __name__ == '__main__') ...
Now, let's create the index.html file inside the templates folder.
We'll use a Jinja2 for loop to iterate over the todos list we passed from our Python code and display each item in a list.
<h1>My To-Do List</h1>
<ul>
{% for todo in todos %}
<li>{{ todo }}</li>
{% endfor %}
</ul>
To add new to-dos, we need an HTML form in our index.html.
This form will send the new task data to a new URL, /add, using the POST method.
<!-- templates/index.html -->
<hr>
<h2>Add a new task</h2>
<form action="/add" method="post">
<input type="text" name="todo_item" required>
<button type="submit">Add Task</button>
</form>
In app.py, we create a new route /add that only accepts POST requests.
It gets the form data, appends it to our todos list, and then redirects the user back to the main page to see the updated list.
# app.py
from flask import Flask, render_template, request, redirect
# ... (app and todos list setup) ...
@app.route('/')
def index():
return render_template('index.html', todos=todos)
@app.route('/add', methods=['POST'])
def add():
new_todo = request.form.get('todo_item')
todos.append(new_todo)
return redirect('./')
# ... (if __name__ == '__main__') ...
Docker is an open-source platform for the containerization of applications.
It solves the classic problem: "It works on my machine, but not on the server."
Think of it like a real-world shipping container. Before them, shipping was a mess. You had different-sized boxes, barrels, and sacks. Now, everything goes into a standard container that can be moved by any crane, ship, or truck in the world.
A Docker container does the same thing for software. It packages everything an application needs into a single, standard unit that runs the same way everywhere.
A container includes everything required to run your application in a predictable way:
The Problem (Without Docker):
Python 3.9 and Flask 2.0. It works perfectly.Python 3.7 and an older version of Flask installed for another project. When you deploy your code, it crashes due to version conflicts.The Solution (With Docker):
Python 3.9, and Flask 2.0 into a Docker container.There are two primary ways to install Docker:
For this course, we will primarily use Docker Desktop and the command line.
The three main components you'll interact with in Docker are:
Docker Hub is a registry for Docker images.
The `docker pull` command downloads an image from a registry (Docker Hub by default) to your local machine.
Let's pull a simple "hello-world" image.
# Pull the 'hello-world' image from Docker Hub
docker pull hello-world
The `docker run` command creates and starts a new container from a specified image.
When you run `hello-world`, it creates a container, prints a message, and then exits.
The `--rm` flag is useful as it automatically removes the container after it exits.
# Run a container from the hello-world image
docker run hello-world
# Run and automatically remove the container on exit
docker run --rm hello-world
If you try to `docker run` an image that you don't have locally, Docker will automatically try to `pull` it from Docker Hub first.
You can also specify a command to execute inside the container.
# If the python image is not local, Docker will pull it first.
# Then it runs the container and executes the python command inside it.
docker run --rm python:3.12-slim python -c "print('Hello from a container!')"
Tags:
Layers:
You can list your local images and running containers from the command line.
docker image ls shows all images on your machine.
docker ps shows only the currently running containers.
To see all containers, including stopped ones, use the `-a` flag.
# List all local images
docker image ls
# List running containers
docker ps
# List all containers (running and stopped)
docker ps -a
You can interact with containers using their ID or auto-generated name.
docker logs shows the log output of a container.
docker stop gracefully stops a running container.
# First, find the container ID with docker ps
docker ps
# View logs for a specific container
docker logs <container_id_or_name>
# Stop a running container
docker stop <container_id_or_name>
Over time, you can accumulate many stopped containers.
The `docker container prune` command is a convenient way to remove all stopped containers at once.
It will ask for confirmation before deleting.
# Remove all stopped containers
docker container prune
You can get an interactive shell inside a running container using `docker exec`.
This is extremely useful for debugging and inspecting the container's environment.
The `-it` flags stand for interactive and TTY (which allocates a pseudo-terminal).
# Start a container in the background (-d for detached)
docker run -d --name my-busybox busybox sleep 3600
# Execute a shell command inside the running container
docker exec -it my-busybox sh
By default, any data created inside a container's filesystem is tied to the life of that container.
If you create a file, then stop and remove the container, that file is gone forever.
How can we save data permanently, like in a database?
Solution: Docker Volumes.
Volumes are managed by Docker and exist outside the container's lifecycle.
You can create a volume and then "mount" it to a specific path inside one or more containers.
The `-v` flag maps `volume-name` to `/path/in/container`.
# 1. Create a named volume
docker volume create my-data-volume
# 2. Run a container and mount the volume
# Maps 'my-data-volume' to the '/data' directory inside the container
docker run -d --name my-container -v my-data-volume:/data busybox sleep 3600
So far, we've only used pre-built images from Docker Hub.
The real power of Docker comes from packaging your own applications.
To do this, we create a special file called a `Dockerfile`.
A `Dockerfile` is a text file that contains all the commands, in order, needed to build a given image.
Let's look at some core instructions for a Python Flask app.
# Use an official Python runtime as a parent image
FROM python:3.12-slim
# Set the working directory in the container to /app
WORKDIR /app
# Copy the requirements file into the container at /app
COPY requirements.txt .
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application code
COPY . .
# Make port 5000 available to the world outside this container
EXPOSE 5000
# Define the command to run the app
CMD ["python3", "app.py"]
Once your `Dockerfile` is ready, you use the `docker build` command to create the image.
# Build the image from the Dockerfile in the current directory
docker build -t flask-app:latest .
To run our custom-built image, we use `docker run` as before.
The crucial addition is the `-p` (publish) flag. It maps a port from your host machine to a port inside the container.
Syntax: `-p <host_port>:<container_port>`.
Now you can access `http://localhost:5000` in your browser to see the app.
# Run the container, mapping port 5000 on the host
# to port 5000 inside the container.
docker run --rm -p 5000:5000 flask-app:latest