├── book
├── ch11-example-setup
│ ├── README.md
│ ├── containers
│ │ ├── core-app
│ │ │ ├── video-collector-docker
│ │ │ │ ├── src
│ │ │ │ │ └── placeholder.md
│ │ │ │ ├── .dockerignore
│ │ │ │ └── Dockerfile
│ │ │ ├── dot-env-template.sh
│ │ │ └── compose.yaml
│ │ ├── base-images
│ │ │ ├── compose.yaml
│ │ │ ├── pythonbase
│ │ │ │ └── Dockerfile
│ │ │ └── linuxbase
│ │ │ │ └── Dockerfile
│ │ └── web-servers
│ │ │ ├── dot-env-template.sh
│ │ │ ├── local-nginx.conf
│ │ │ ├── compose.yaml
│ │ │ └── nginx-base-configs
│ │ │ └── video-collector.nginx
│ ├── scripts
│ │ ├── update-images.sh
│ │ ├── deploy.sh
│ │ ├── update-source.sh
│ │ ├── create-docker-compose-service.sh
│ │ └── build_containers.sh
│ ├── shell
│ │ └── zshrc
│ └── setup-host-server.sh
└── ch08-docker-performance-tips
│ └── faster-docker-example
│ ├── webapp
│ ├── src
│ │ ├── requirements.piptools
│ │ ├── granian.conf
│ │ ├── app.py
│ │ └── requirements.txt
│ ├── .dockerignore
│ └── Dockerfile
│ ├── compose.yaml
│ ├── linuxbase
│ └── Dockerfile
│ ├── pythonbase
│ └── Dockerfile
│ └── readme.md
├── images
└── devops-book-web-hero.webp
├── galleries
├── figure-gallery
│ ├── figures
│ │ ├── 12-01-hugo.png
│ │ ├── 07-03-glances.jpg
│ │ ├── 10-01-cdn-pops.jpg
│ │ ├── 11-11-nginx-up.png
│ │ ├── 14-03-courses.jpg
│ │ ├── 13-01-frameworks.png
│ │ ├── 14-02-us-podcast.jpg
│ │ ├── 14-05-live-site.jpg
│ │ ├── 06-01-server-tree.png
│ │ ├── 06-02-events-chart.png
│ │ ├── 06-03-uptime-kuma.png
│ │ ├── 07-02-btop-options.jpg
│ │ ├── 10-02-bunny-stats.png
│ │ ├── 11-02-bash-basics.png
│ │ ├── 11-04-pls-listing.png
│ │ ├── 11-05-docker-layers.png
│ │ ├── 11-12-deploy-output.png
│ │ ├── 14-01-other-podcast.jpg
│ │ ├── 14-04-live-youtube.jpg
│ │ ├── 14-06-twitter-share.jpg
│ │ ├── 07-01-btop-dashboard.jpg
│ │ ├── 11-07-video-collector.png
│ │ ├── 11-10-service-status.png
│ │ ├── 12-05-mkennedy-codes.png
│ │ ├── 13-02-webapp-to-quart.png
│ │ ├── 13-03-quart-to-async.png
│ │ ├── 15-02-hetzner-server.png
│ │ ├── 05-01-architecture-tiers.png
│ │ ├── 06-04-status-talkpython.png
│ │ ├── 11-01-hetzner-projects.png
│ │ ├── 12-02-berkmansolutions-1.png
│ │ ├── 12-03-berkmansolutions-2.png
│ │ ├── 12-04-berkmansolutions-3.png
│ │ ├── 12-06-talk-python-blog.png
│ │ ├── 15-01-hetzner-dashboard.png
│ │ ├── 16-01-stack-native-final.png
│ │ ├── 05-02-granian-lots-of-granian.png
│ │ ├── 07-04-docker-cluster-monitor.jpg
│ │ ├── 11-08-build-video-collector.png
│ │ ├── 11-09-running-video-collector.png
│ │ ├── 03-01-workload-talkpython-apps.png
│ │ ├── 15-03-bandwidth-and-cpu-compared.png
│ │ ├── 16-01-cloud-native-diagram-final.png
│ │ ├── 16-01-stack-native-to-scale-final.png
│ │ ├── 11-03-bash-basics.pngohmyzsh-completion.png
│ │ └── 11-06-docker-compose-output-base-images.png
│ └── README.md
├── links-gallery
│ └── readme.md
└── code-gallery
│ └── README.md
├── ruff.toml
├── LICENSE
├── change-log.md
├── README.md
├── .gitignore
└── WARP.md
/book/ch11-example-setup/README.md:
--------------------------------------------------------------------------------
1 | # Python in Production Example Setup
2 |
3 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/core-app/video-collector-docker/src/placeholder.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/webapp/src/requirements.piptools:
--------------------------------------------------------------------------------
1 | flask
2 | setproctitle
3 | uvloop
4 |
--------------------------------------------------------------------------------
/images/devops-book-web-hero.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/images/devops-book-web-hero.webp
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/webapp/src/granian.conf:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8080
3 |
4 | routes:
5 | - path: /app
6 | handler: app:app.run()
7 |
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/12-01-hugo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/12-01-hugo.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/07-03-glances.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/07-03-glances.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/10-01-cdn-pops.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/10-01-cdn-pops.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-11-nginx-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-11-nginx-up.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/14-03-courses.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/14-03-courses.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/13-01-frameworks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/13-01-frameworks.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/14-02-us-podcast.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/14-02-us-podcast.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/14-05-live-site.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/14-05-live-site.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/06-01-server-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/06-01-server-tree.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/06-02-events-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/06-02-events-chart.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/06-03-uptime-kuma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/06-03-uptime-kuma.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/07-02-btop-options.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/07-02-btop-options.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/10-02-bunny-stats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/10-02-bunny-stats.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-02-bash-basics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-02-bash-basics.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-04-pls-listing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-04-pls-listing.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-05-docker-layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-05-docker-layers.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-12-deploy-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-12-deploy-output.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/14-01-other-podcast.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/14-01-other-podcast.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/14-04-live-youtube.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/14-04-live-youtube.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/14-06-twitter-share.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/14-06-twitter-share.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/07-01-btop-dashboard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/07-01-btop-dashboard.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-07-video-collector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-07-video-collector.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-10-service-status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-10-service-status.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/12-05-mkennedy-codes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/12-05-mkennedy-codes.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/13-02-webapp-to-quart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/13-02-webapp-to-quart.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/13-03-quart-to-async.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/13-03-quart-to-async.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/15-02-hetzner-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/15-02-hetzner-server.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/05-01-architecture-tiers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/05-01-architecture-tiers.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/06-04-status-talkpython.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/06-04-status-talkpython.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-01-hetzner-projects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-01-hetzner-projects.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/12-02-berkmansolutions-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/12-02-berkmansolutions-1.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/12-03-berkmansolutions-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/12-03-berkmansolutions-2.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/12-04-berkmansolutions-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/12-04-berkmansolutions-3.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/12-06-talk-python-blog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/12-06-talk-python-blog.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/15-01-hetzner-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/15-01-hetzner-dashboard.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/16-01-stack-native-final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/16-01-stack-native-final.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/05-02-granian-lots-of-granian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/05-02-granian-lots-of-granian.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/07-04-docker-cluster-monitor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/07-04-docker-cluster-monitor.jpg
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-08-build-video-collector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-08-build-video-collector.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-09-running-video-collector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-09-running-video-collector.png
--------------------------------------------------------------------------------
/book/ch11-example-setup/scripts/update-images.sh:
--------------------------------------------------------------------------------
1 | # --------------------- DOCKER BASES ----------------------------------
2 | echo "Updating Docker base images"
3 | docker pull ubuntu:latest
4 | docker pull nginx:latest
5 |
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/03-01-workload-talkpython-apps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/03-01-workload-talkpython-apps.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/15-03-bandwidth-and-cpu-compared.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/15-03-bandwidth-and-cpu-compared.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/16-01-cloud-native-diagram-final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/16-01-cloud-native-diagram-final.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/16-01-stack-native-to-scale-final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/16-01-stack-native-to-scale-final.png
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/webapp/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | **/.git
3 | **/.github
4 | **/.fleet
5 | **/.vscode
6 | **/.idea
7 | **/src/logos
8 | **/src/docs
9 | **/requirements-development.txt
10 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/core-app/video-collector-docker/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | **/.git
3 | **/.github
4 | **/.fleet
5 | **/.vscode
6 | **/.idea
7 | **/src/logos
8 | **/src/docs
9 | **/requirements-development.txt
10 |
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-03-bash-basics.pngohmyzsh-completion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-03-bash-basics.pngohmyzsh-completion.png
--------------------------------------------------------------------------------
/galleries/figure-gallery/figures/11-06-docker-compose-output-base-images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikeckennedy/talk-python-in-production-devops-book/HEAD/galleries/figure-gallery/figures/11-06-docker-compose-output-base-images.png
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/core-app/dot-env-template.sh:
--------------------------------------------------------------------------------
1 | # Template file for nginx set up.
2 | # Copy to local .env file with updated values
3 |
4 | # Here are some options:
5 |
6 | # Locally for development:
7 | APP_LOGS=./video-collector-data/logs
8 |
9 | # Some candidate production settings
10 | APP_LOGS=/cluster-data/video-collector-data/logs
11 |
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/webapp/src/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 |
3 | app = Flask(__name__)
4 |
5 |
6 | @app.route('/')
7 | def hello_world():
8 | return '
Hello, World from faster Docker and Python with uv python!
'
9 |
10 |
11 | if __name__ == '__main__':
12 | app.run(host='127.0.0.1', port=8080, debug=False)
13 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/base-images/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | python-example-base:
4 | build: ./pythonbase
5 | container_name: python-example-base
6 | image: python-example-base
7 | depends_on:
8 | - linux-example-base
9 |
10 | linux-example-base:
11 | build: ./linuxbase
12 | container_name: linux-example-base
13 | image: linux-example-base
14 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/shell/zshrc:
--------------------------------------------------------------------------------
1 | # Add these to the end of your rc file:
2 |
3 | alias http='docker run -it --rm --net=host clue/httpie'
4 | alias glances='docker run --rm --name glances -v /var/run/docker.sock:/var/run/docker.sock:ro -v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro --pid host --network host -it docker.io/nicolargo/glances'
5 | alias deploy="/cluster-src/book/ch11-example-setup/scripts/deploy.sh"
6 | alias dc="docker compose"
7 | alias lls="/bin/ls -G"
8 | alias ls="pls"
9 |
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/webapp/src/requirements.txt:
--------------------------------------------------------------------------------
1 | # This file was autogenerated by uv via the following command:
2 | # uv pip compile requirements.piptools --output-file requirements.txt
3 | blinker==1.9.0
4 | # via flask
5 | click==8.3.0
6 | # via flask
7 | flask==3.1.2
8 | # via -r requirements.piptools
9 | itsdangerous==2.2.0
10 | # via flask
11 | jinja2==3.1.6
12 | # via flask
13 | markupsafe==3.0.3
14 | # via
15 | # flask
16 | # jinja2
17 | # werkzeug
18 | setproctitle==1.3.7
19 | # via -r requirements.piptools
20 | uvloop==0.22.1
21 | # via -r requirements.piptools
22 | werkzeug==3.1.3
23 | # via flask
24 |
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/compose.yaml:
--------------------------------------------------------------------------------
1 | # Build with the command:
2 | #
3 | # docker compose build linuxbase-faster && docker compose build pythonbase-faster && docker compose build uv-docker-app
4 | #
5 | # See the README.md file for details and background info.
6 | #
7 | services:
8 | linuxbase-faster:
9 | build: ./linuxbase
10 | container_name: linuxbase-faster
11 | image: linuxbase-faster
12 |
13 | pythonbase-faster:
14 | build: ./pythonbase
15 | container_name: pythonbase-faster
16 | image: pythonbase-faster
17 |
18 | uv-docker-app:
19 | build: ./webapp
20 | container_name: uv-docker-app
21 | image: uv-docker-app
22 | ports:
23 | - "127.0.0.1:8080:8080"
24 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/scripts/deploy.sh:
--------------------------------------------------------------------------------
1 | # ################ Set up environment for shared vars #####################
2 | set -eu pipefail # Enable strict error handling
3 | # Script's working directory
4 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5 | # echo "SCRIPT_DIR ${SCRIPT_DIR}"
6 | RELATIVE_PATH="${SCRIPT_DIR}/../../.."
7 | # echo "RELATIVE_PATH ${RELATIVE_PATH}"
8 | # Repo cloned here
9 | REPO_PATH=$(realpath "$RELATIVE_PATH")
10 | # echo "REPO_PATH ${REPO_PATH}"
11 | # ############### END Set up environment ##################################
12 |
13 |
14 | ${REPO_PATH}/book/ch11-example-setup/scripts/update-images.sh
15 | ${REPO_PATH}/book/ch11-example-setup/scripts/update-source.sh
16 | ${REPO_PATH}/book/ch11-example-setup/scripts/build_containers.sh
17 |
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/linuxbase/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest
2 |
3 | ENV LANG=en_US.UTF-8
4 | ENV LANGUAGE=en_US:en
5 | ENV LC_ALL=en_US.UTF-8
6 | ENV TZ="America/Los_Angeles"
7 | ENV DEBIAN_FRONTEND=noninteractive
8 |
9 | RUN apt-get update
10 | RUN apt-get upgrade -y
11 | # RUN apt-get dist-upgrade
12 |
13 | RUN apt-get -y install locales git curl wget
14 | RUN locale-gen en_US.UTF-8
15 |
16 | RUN apt-get install -y gcc
17 | RUN apt-get install -y clang
18 | RUN apt-get install -y build-essential
19 | RUN apt-get install -y openssl
20 |
21 | # Install Oh My ZSH: Uses "robbyrussell" theme (original Oh My Zsh theme), with no plugins
22 | RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.1/zsh-in-docker.sh)" -- \
23 | -t robbyrussell
24 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/web-servers/dot-env-template.sh:
--------------------------------------------------------------------------------
1 | # Template file for nginx set up.
2 | # Copy to local .env file with updated values
3 |
4 | # Here are some options:
5 |
6 | # Locally for development:
7 | NGINX_STATIC=./cluster-data/nginx/static
8 | NGINX_LOGS=./cluster-data/nginx/logs
9 | NGINX_SITES_ENABLED=./nginx-base-configs
10 | NGINX_LETS_ENCRYPT_ETC=./cluster-data/nginx/letsencrypt-etc
11 | NGINX_LETSENCRYPT_WWW=./cluster-data/nginx/letsencrypt-www
12 | CERTBOT_WWW=./cluster-data/nginx/certbot/www
13 |
14 | # Some candidate production settings
15 | NGINX_STATIC=/cluster-data/nginx/static
16 | NGINX_LOGS=/cluster-data/nginx/logs
17 | NGINX_SITES_ENABLED=./nginx-base-configs
18 | NGINX_LETS_ENCRYPT_ETC=/cluster-data/nginx/letsencrypt-etc
19 | NGINX_LETSENCRYPT_WWW=/cluster-data/nginx/letsencrypt-www
20 | CERTBOT_WWW=/cluster-data/nginx/certbot/www
21 |
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/pythonbase/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM linuxbase-faster:latest
2 |
3 | RUN mkdir /apps
4 |
5 | ENV PATH=/venv/bin:$PATH
6 | ENV PATH=/root/.cargo/bin:$PATH
7 | ENV PATH="/root/.local/bin/:$PATH"
8 |
9 | RUN curl -LsSf https://astral.sh/uv/install.sh | sh
10 |
11 | # set up a virtual env to use for whatever app is destined for this container.
12 | # RUN uv venv --python 3.13.0 --seed /venv
13 | RUN uv venv --python 3.13.0 /venv
14 |
15 | RUN echo "\nsource /venv/bin/activate\n" >> /root/.zshrc
16 | RUN uv --version
17 |
18 |
19 | # Install some handy utilities if you want them for debugging within the container
20 | RUN --mount=type=cache,target=/root/.cache uv pip install httpie glances pls
21 |
22 | # Install some of the common packages that (many) don't change often so later images build faster.
23 |
24 | RUN echo "We're good:"
25 | RUN /venv/bin/python --version
26 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/core-app/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | video-collector:
4 | image: video-collector
5 | container_name: video-collector
6 | restart: unless-stopped
7 | ports:
8 | - "127.0.0.1:15000:15000"
9 | volumes:
10 | - "${APP_LOGS}:/logs"
11 | build: video-collector-docker
12 | deploy:
13 | resources:
14 | limits:
15 | # noinspection ComposeUnknownValues
16 | memory: 1G
17 | # healthcheck:
18 | # test: nohup curl -A "Docker Health Agent (via curl)" --fail http://localhost:15000?app=docker-compose-healthcheck || exit 1
19 | # interval: 60s
20 | # retries: 5
21 | # start_period: 25s
22 | # timeout: 30s
23 | networks:
24 | cluster-network:
25 | ipv4_address: 174.44.0.100
26 |
27 |
28 | networks:
29 | cluster-network:
30 | name: cluster-network
31 | external: true
32 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/base-images/pythonbase/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM linux-example-base:latest
2 |
3 | RUN mkdir /apps
4 |
5 | ENV PATH=/venv/bin:$PATH
6 | ENV PATH=/root/.cargo/bin:$PATH
7 | ENV PATH="/root/.local/bin/:$PATH"
8 |
9 | # Install Rust/Cargo for local builds of Rust-based Python packages
10 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
11 |
12 | # Install uv
13 | RUN curl -LsSf https://astral.sh/uv/install.sh | sh
14 |
15 | # Set up a virtual env to use for whatever app is destined for this container.
16 | RUN uv venv --python 3.14 /venv
17 |
18 | # Have the venv activate itself if we login via docker run/exec:
19 | RUN echo "\nsource /venv/bin/activate\n" >> /root/.zshrc
20 |
21 | # Install some handy utilities if you want them for debugging within the container
22 | RUN --mount=type=cache,target=/root/.cache uv pip install httpie pls
23 |
24 | RUN /venv/bin/python --version
25 |
26 | RUN echo "We're good."
27 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/base-images/linuxbase/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest
2 |
3 | ENV LANG=en_US.UTF-8
4 | ENV LANGUAGE=en_US:en
5 | ENV LC_ALL=en_US.UTF-8
6 | ENV TZ="America/Los_Angeles"
7 | ENV DEBIAN_FRONTEND=noninteractive
8 |
9 | RUN apt-get update
10 | RUN apt-get upgrade -y
11 |
12 | RUN echo "Heads up - this section takes a little while but only builds once on the machine until Linux updates."
13 |
14 | RUN apt-get -y install curl
15 | RUN apt-get -y install wget
16 | RUN apt-get -y install locales
17 | RUN locale-gen en_US.UTF-8
18 |
19 | RUN apt-get -y install git
20 | RUN apt-get install -y gcc
21 | RUN apt-get install -y clang
22 | # RUN apt-get install -y build-essential
23 | RUN apt-get install -y openssl
24 |
25 | # Install Oh My ZSH: Uses "robbyrussell" theme (original Oh My Zsh theme), with no plugins
26 | RUN apt-get install -y zsh
27 | RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.1/zsh-in-docker.sh)" -- \
28 | -t robbyrussell \
29 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/web-servers/local-nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes auto;
3 |
4 | error_log /var/log/nginx/error.log notice;
5 | pid /var/run/nginx.pid;
6 |
7 |
8 | # More about rlimits here
9 | # https://www.cloudbees.com/blog/tuning-nginx
10 | #
11 | worker_rlimit_nofile 2048;
12 | events {
13 | worker_connections 2048;
14 | }
15 |
16 |
17 | http {
18 | include /etc/nginx/mime.types;
19 | include /etc/nginx/sites-enabled/*;
20 | default_type application/octet-stream;
21 |
22 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
23 | '$status $body_bytes_sent "$http_referer" '
24 | '"$http_user_agent" "$http_x_forwarded_for"';
25 |
26 | access_log /var/log/nginx/access.log main;
27 |
28 | sendfile on;
29 | #tcp_nopush on;
30 |
31 | keepalive_timeout 65;
32 |
33 | #gzip on;
34 |
35 | include /etc/nginx/conf.d/*.conf;
36 | }
37 |
--------------------------------------------------------------------------------
/ruff.toml:
--------------------------------------------------------------------------------
1 | # [ruff]
2 | line-length = 120
3 | format.quote-style = "single"
4 |
5 | # Enable Pyflakes `E` and `F` codes by default.
6 | lint.select = ["E", "F", "I"]
7 | lint.ignore = []
8 |
9 | # Exclude a variety of commonly ignored directories.
10 | exclude = [
11 | ".bzr",
12 | ".direnv",
13 | ".eggs",
14 | ".git",
15 | ".hg",
16 | ".mypy_cache",
17 | ".nox",
18 | ".pants.d",
19 | ".ruff_cache",
20 | ".svn",
21 | ".tox",
22 | "__pypackages__",
23 | "_build",
24 | "buck-out",
25 | "build",
26 | "dist",
27 | "node_modules",
28 | ".env",
29 | ".venv",
30 | "venv",
31 | "typings/**/*.pyi",
32 | ]
33 | lint.per-file-ignores = { }
34 |
35 | # Allow unused variables when underscore-prefixed.
36 | # dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
37 |
38 | # Assume Python 3.13.
39 | target-version = "py313"
40 |
41 | #[tool.ruff.mccabe]
42 | ## Unlike Flake8, default to a complexity level of 10.
43 | lint.mccabe.max-complexity = 10
44 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/scripts/update-source.sh:
--------------------------------------------------------------------------------
1 | # ################ Set up environment for shared vars #####################
2 | set -eu pipefail # Enable strict error handling
3 | # Script's working directory
4 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5 | RELATIVE_PATH="${SCRIPT_DIR}/../../.."
6 | # Repo cloned here
7 | REPO_PATH=$(realpath "$RELATIVE_PATH")
8 | # ############### END Set up environment ##################################
9 |
10 | echo "Updating infrastructure code"
11 | cd ${REPO_PATH}/book/
12 | git pull
13 | echo "infrastructure code done"
14 |
15 | echo "Updating HTMX sample app code"
16 | cd ${REPO_PATH}/book/ch11-example-setup/containers/core-app/video-collector-docker/src/htmx-python-course
17 | git pull
18 | echo "HTMX sample app code done"
19 |
20 | echo "Updating static files"
21 | cp -r ${REPO_PATH}/book/ch11-example-setup/containers/core-app/video-collector-docker/src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector/static/* /cluster-data/nginx/static/video-collector
22 | echo "Static files done"
--------------------------------------------------------------------------------
/book/ch08-docker-performance-tips/faster-docker-example/webapp/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM pythonbase-faster:latest
2 |
3 | RUN mkdir /app
4 |
5 |
6 | COPY src/requirements.txt /app
7 | WORKDIR "/app"
8 | RUN --mount=type=cache,target=/root/.cache uv pip install -r requirements.txt
9 |
10 | COPY src/ /app
11 |
12 | # Make sure we have the latest web servers if the bases are cached.
13 | RUN --mount=type=cache,target=/root/.cache uv pip install --upgrade granian[pname]
14 |
15 | # Expose the port that Granián will use
16 | EXPOSE 8080
17 |
18 | # Set the command to run when the container starts (Granián)
19 | ENTRYPOINT [ \
20 | "/venv/bin/granian",\
21 | "app:app", \
22 | "--host","0.0.0.0", \
23 | "--port","8080", \
24 | "--interface","wsgi", \
25 | "--no-ws", \
26 | "--workers","2", \
27 | "--backpressure", "40", \
28 | "--runtime-mode", "mt", \
29 | "--runtime-threads", "4",\
30 | "--loop","uvloop", \
31 | "--log-level","info",\
32 | "--log", \
33 | "--process-name", \
34 | "granian [uv-docker-app]" \
35 | ]
36 |
37 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/containers/core-app/video-collector-docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python-example-base:latest
2 |
3 | WORKDIR "/app"
4 |
5 | COPY ./src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector/requirements.txt /app
6 | RUN uv self update
7 | RUN --mount=type=cache,target=/root/.cache uv pip install -r requirements.txt
8 |
9 | COPY src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector /app
10 |
11 | # Make sure we have the latest web server if the bases are cached.
12 | RUN --mount=type=cache,target=/root/.cache uv pip install --upgrade "granian[pname]"
13 |
14 | # Set the command to run when the container starts (Granián)
15 | ENTRYPOINT [ \
16 | "/venv/bin/granian",\
17 | "app:app", \
18 | "--host","0.0.0.0", \
19 | "--port","15000", \
20 | "--interface","wsgi", \
21 | "--no-ws", \
22 | "--workers","2", \
23 | "--backpressure", "40", \
24 | "--runtime-mode", "mt", \
25 | "--runtime-threads", "4",\
26 | "--log-level","info",\
27 | "--log", \
28 | "--process-name", \
29 | "granian [video-collector]" \
30 | ]
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Michael Kennedy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/scripts/create-docker-compose-service.sh:
--------------------------------------------------------------------------------
1 | # Create a systemd service that autostarts & manages a docker-compose instance in the current directory
2 | # by Uli Köhler - https://techoverflow.net
3 | # Licensed as CC0 1.0 Universal
4 | SERVICENAME=$(basename $(pwd))
5 |
6 | echo "Creating systemd service at /etc/systemd/system/${SERVICENAME}.service"
7 | # Create systemd service file
8 | sudo cat >/etc/systemd/system/$SERVICENAME.service <
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Talk Python in Production Book
2 | ## Code & Resources Companion Repository
3 |
4 | [](https://talkpython.fm/books/python-in-production)
5 |
6 | Welcome to Python in Production, the code gallery and resources that accompany the book “[Talk Python in Production](https://talkpython.fm/books/python-in-production)” – a cloud‑agnostic guide to building, scaling, and managing your own Python infrastructure. This repository exists to help readers and curious developers explore the hands‑on examples, scripts, configuration files, and high‑resolution figures described in the book. If you discovered this project first, head over to the book site to read more about the [philosophy]() and to purchase or [read the book online](https://talkpython.fm/books/python-in-production).
7 |
8 | ## About the book
9 |
10 | [Talk Python in Production](https://talkpython.fm/books/python-in-production) is a practical roadmap for running Python applications in the real world. Instead of being locked into hyperscale cloud services or wrestling with brittle, complex DevOps tooling, the book advocates a stack‑native approach: run your own software on a single, powerful server using Docker and proven open‑source tools. You’ll learn how to containerize your apps, proxy them through NGINX, integrate a CDN, and manage the entire stack yourself. It’s aimed at Python developers who want more control over their deployments, lower infrastructure costs, and a deeper understanding of how things work in production.
11 |
12 | The book covers a wide range of topics, including:
13 |
14 | * Architectures and infrastructure evolution – real‑world hosting journeys from PaaS platforms to multi‑VM setups and beyond.
15 | * One big server strategy – why a well‑configured VM running multiple containers often outperforms a fleet of tiny instances.
16 | * Docker & Docker Compose – containerizing Python web apps, optimizing Dockerfiles, and orchestrating services.
17 | * NGINX & Let’s Encrypt – reverse proxy basics, HTTPS certificates, and secure, efficient routing of traffic.
18 | * Self‑hosted services – running your own analytics, monitoring, and other tools to avoid vendor lock‑in.
19 | * Performance and scaling – caching, CDNs, multiple worker processes, and handling spikes without Kubernetes.
20 | * Migrations & framework choices – moving from legacy frameworks to modern async ones and consolidating servers.
21 |
22 | Whether you’re migrating existing applications or starting fresh, the book delivers step‑by‑step tutorials, cost‑saving tips, and lessons learned from years of running high‑traffic Python sites.
23 |
24 | ## Contributing
25 |
26 | Issues, pull requests, and discussions are welcome! If you have questions, ideas for improvements, or success stories about adopting a stack‑native workflow, head over to GitHub Discussions and join the conversation. Please keep contributions focused on improving the examples or clarifying the instructions.
27 |
28 | ## Learn more
29 |
30 | **Read or buy the book** – [talkpython.fm/books/python-in-production](https://talkpython.fm/books/python-in-production) contains the full text, first third of the book online for free, and purchase options.
31 |
32 | **Related courses** – Check out [Talk Python Training](https://training.talkpython.fm/courses/all) for courses on Python, web development, and deploying apps.
33 |
34 | **Podcast & community** – [Listen to the Talk Python To Me podcast](https://talkpython.fm/episodes/all) and join the community for in‑depth interviews with Python and software professionals.
35 |
36 | ## Build your own stack
37 |
38 | By following the examples in this repository, you’ll learn to deploy Python applications confidently, control your infrastructure costs, and avoid the complexity of hyperscale cloud platforms. Happy shipping!
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110 | .pdm.toml
111 | .pdm-python
112 | .pdm-build/
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | .idea/
163 |
164 | cluster-data/
165 | /book/ch11-example-setup/containers/core-app/video-collector-docker/src/
166 |
--------------------------------------------------------------------------------
/book/ch11-example-setup/setup-host-server.sh:
--------------------------------------------------------------------------------
1 | # This script is meant as a guide to setup a new machine to run our docker cluster.
2 | # RUN THIS ONLY ONCE.
3 |
4 |
5 | ############################################################
6 | # This script is not meant to be run directly.
7 | # Take it piece by piece and execute the blocks
8 | echo ""
9 | echo "Error: This script is not meant to be run directly."
10 | echo "Take it piece by piece and execute the blocks."
11 | echo "For example, there are sections where you need to fill out info"
12 | echo "like your name and email before running that block of code."
13 | echo "Plus running each block will give you a chance to understand it better."
14 | echo ""
15 | exit 0
16 |
17 |
18 | ############################################################
19 | # Setup the server itself including ohmyzsh
20 | apt-get update
21 | apt-get upgrade -y
22 | apt-get install zsh -y
23 | sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
24 |
25 | apt install btop -y
26 | apt install ca-certificates -y
27 | apt install curl wget -y
28 | apt install git -y
29 | apt install tree -y
30 |
31 | git config --global credential.helper cache
32 | git config --global credential.helper 'cache --timeout=720000'
33 |
34 | # TODO: Enter your name and email for git in the placeholders.
35 | git config --global user.email "YOUR_EMAIL"
36 | git config --global user.name "YOUR_NAME"
37 |
38 |
39 | # install uv for local tool management
40 | curl -LsSf https://astral.sh/uv/install.sh | sh
41 | # install pls via uv:
42 | uv tool install pls
43 |
44 | # Add the following lines to your ~/.zshrc file:
45 | alias http='docker run -it --rm --net=host clue/httpie'
46 | alias glances='docker run --rm --name glances -v /var/run/docker.sock:/var/run/docker.sock:ro -v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro --pid host --network host -it docker.io/nicolargo/glances'
47 | alias deploy="/cluster-src/book/ch11-example-setup/scripts/deploy.sh"
48 | alias dc="docker compose"
49 | alias lls="/bin/ls -G"
50 | alias ls="pls"
51 |
52 | # Then reload the config:
53 | source ~/.zshrc
54 |
55 | ############################################################
56 | # Setup docker on the server.
57 |
58 | # Add Docker's official GPG key:
59 | sudo apt-get update
60 | sudo apt-get install ca-certificates curl -y
61 | sudo install -m 0755 -d /etc/apt/keyrings
62 | sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
63 | sudo chmod a+r /etc/apt/keyrings/docker.asc
64 |
65 | # Add the repository to Apt sources:
66 | echo \
67 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
68 | $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
69 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
70 | sudo apt-get update
71 |
72 | # To install the latest version of Docker
73 | sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
74 |
75 | # Change Docker data file to mounted volume if wanted, this will allow you to mount a
76 | # separate volume much bigger than your hard drive and keep the docker working volumes and images
77 | # there. This is especially useful if you have very large DB files in a persistent Docker volume.
78 | # See https://stackoverflow.com/questions/36014554/how-to-change-the-default-location-for-docker-create-volume-command
79 | # Before you run anything related to Docker, be sure to change the docker working data to
80 | # the mounted volume if you intend to do that.
81 |
82 | # Test Docker by running hello-world:
83 | docker run hello-world
84 |
85 |
86 |
87 |
88 | ############################################################
89 | # Create the persistent docker elements (network, disks, etc)
90 | docker network create -d bridge cluster-network --subnet=174.44.0.0/16
91 | # Example: "docker volume create NAME" if you need a persistent volume,
92 | # our example doesn't use one.
93 |
94 |
95 |
96 | ############################################################
97 | # Set up the infrastructure (this project) and docker files
98 |
99 | # Download the infrastructure files (this repo)
100 | # TODO: MAKE SURE TO USE YOUR OWN FORK OF THE REPO BELOW.
101 | # You won't be able to test updates if you use the base one since you can't change it on demand.
102 | git clone https://github.com/mikeckennedy/talk-python-in-production-devops-book /cluster-src/
103 |
104 |
105 | # Copy the app src
106 | # TODO: MAKE SURE TO USE YOUR OWN FORK OF THE REPO https://github.com/talkpython/htmx-python-course BELOW.
107 | # You won't be able to test updates if you use the base one since you can't change it on demand.
108 | cd /cluster-src/book/ch11-example-setup/containers/core-app/video-collector-docker/src
109 | git clone https://github.com/talkpython/htmx-python-course
110 |
111 |
112 | # ###############
113 | # Make scripts runnable
114 | chmod +x /cluster-src/book/ch11-example-setup/scripts/*.sh
115 |
116 |
117 | ############################################################
118 | # Make the static folders for data exchange between the
119 | # containers, git updates, and data exports
120 | mkdir -p /cluster-data/
121 | cd /cluster-data/
122 |
123 | # The following folders are assuming the production settings
124 | # from the ./ch11-example-setup/containers/web-servers/dot-env-template.sh file
125 | # You MUST create a .env file in that folder and copy the values from
126 | # dot-env-template.sh with the proper adjustments before anything will build / work.
127 |
128 | # Make the data folders for exchange:
129 | mkdir -p /cluster-data/nginx/static
130 | mkdir -p /cluster-data/nginx/logs
131 | mkdir -p /cluster-data/nginx/sites-enabled
132 | mkdir -p /cluster-data/nginx/letsencrypt-etc
133 | mkdir -p /cluster-data/nginx/letsencrypt-www
134 | mkdir -p /cluster-data/nginx/certbot/www
135 |
136 | mkdir -p /cluster-data/logs/video-collector
137 |
138 | # Copy the NGINX config files into NGINX's config folder we mapped into Docker:
139 | cp /cluster-src/book/ch11-example-setup/containers/web-servers/nginx-base-configs/video-collector.nginx /cluster-data/nginx/sites-enabled
140 | # Copy the static files from our example app into the static folder we're mapping to NGINX (in the compose.yaml later)
141 | cp -r /cluster-src/book/ch11-example-setup/containers/core-app/video-collector-docker/src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector/static /cluster-data/nginx/static/video-collector
142 |
143 | # Add to /etc/hosts on the host machine (so we can test requests against the domain)
144 | 127.0.0.1 video-collector-test.com
145 |
146 | ############################################################
147 | # Run docker compose cluster as systemd service
148 | cd /cluster-src/book/ch11-example-setup/containers/core-app
149 | bash /cluster-src/book/ch11-example-setup/scripts/create-docker-compose-service.sh
150 |
151 | cd /cluster-src/book/ch11-example-setup/containers/web-servers
152 | bash /cluster-src/book/ch11-example-setup/scripts/create-docker-compose-service.sh
153 |
--------------------------------------------------------------------------------
/WARP.md:
--------------------------------------------------------------------------------
1 | # WARP.md
2 |
3 | This file provides guidance to WARP (warp.dev) when working with code in this repository.
4 |
5 | ## Table of Contents
6 |
7 | 1. [Introduction & Purpose](#introduction--purpose)
8 | 2. [Repository Layout](#repository-layout)
9 | 3. [System Architecture](#system-architecture)
10 | 4. [Quick Start](#quick-start)
11 | 5. [Common Commands](#common-commands)
12 | 6. [Environment & Configuration](#environment--configuration)
13 | 7. [Development Workflow](#development-workflow)
14 | 8. [Helper Scripts Reference](#helper-scripts-reference)
15 | 9. [Testing & Linting Guidelines](#testing--linting-guidelines)
16 | 10. [Contributing & Support](#contributing--support)
17 |
18 | ## Introduction & Purpose
19 |
20 | This is the companion repository for **"Talk Python in Production"** by Michael Kennedy - a cloud-agnostic guide to building, scaling, and managing Python infrastructure using a stack-native approach. The repository demonstrates the book's core philosophy:
21 |
22 | - **Single powerful server** rather than many small instances
23 | - **Docker containerization** with proper layering and caching
24 | - **Self-hosted services** to avoid vendor lock-in
25 | - **NGINX reverse proxy** with Let's Encrypt SSL
26 | - **Production-ready deployment** with systemd integration
27 |
28 | The examples focus on running your own infrastructure on a single, well-configured VM using Docker and proven open-source tools.
29 |
30 | ## Repository Layout
31 |
32 | ```
33 | devops-book-public/
34 | ├── book/ # Chapter-specific resources and examples
35 | │ ├── ch08-docker-performance-tips/ # Docker optimization examples
36 | │ └── ch11-example-setup/ # Complete production setup guide
37 | │ ├── containers/ # Docker infrastructure
38 | │ │ ├── base-images/ # Foundational Docker images
39 | │ │ ├── core-app/ # Video Collector demo app
40 | │ │ └── web-servers/ # NGINX + Certbot setup
41 | │ ├── scripts/ # Deployment and automation scripts
42 | │ └── setup-host-server.sh # Initial server configuration
43 | ├── galleries/ # Reference materials from the book
44 | │ ├── code-gallery/ # All code snippets by chapter
45 | │ ├── figure-gallery/ # High-resolution book figures
46 | │ └── links-gallery/ # External links and resources
47 | ├── images/ # Repository assets
48 | ├── README.md # Main repository overview
49 | └── ruff.toml # Python linting configuration
50 | ```
51 |
52 | ## System Architecture
53 |
54 | The repository demonstrates a three-tier containerized architecture:
55 |
56 | ```
57 | ┌─────────────────────────────────────────────────┐
58 | │ External Traffic (Port 80/443) │
59 | └─────────────────┬───────────────────────────────┘
60 | │
61 | ┌─────────────────▼───────────────────────────────┐
62 | │ NGINX Reverse Proxy Container │
63 | │ • SSL termination (Let's Encrypt) │
64 | │ • Static file serving │
65 | │ • Request routing │
66 | └─────────────────┬───────────────────────────────┘
67 | │ cluster-network (174.44.0.0/16)
68 | ┌─────────────────▼───────────────────────────────┐
69 | │ Python Application Container (174.44.0.100) │
70 | │ • Granian ASGI server (production-ready) │
71 | │ • Flask/Quart web application │
72 | │ • uv for fast dependency management │
73 | └─────────────────┬───────────────────────────────┘
74 | │
75 | ┌─────────────────▼───────────────────────────────┐
76 | │ Base Images (Layered) │
77 | │ • linux-example-base: Ubuntu + tools │
78 | │ • python-example-base: Python 3.13 + uv │
79 | └─────────────────────────────────────────────────┘
80 | ```
81 |
82 | **Key Components:**
83 | - **External Docker Network**: `cluster-network` (174.44.0.0/16) for inter-container communication
84 | - **Static IP Assignment**: Application container gets fixed IP for NGINX routing
85 | - **Volume Mounts**: Shared logs, static files, and SSL certificates
86 | - **Systemd Integration**: Containers run as system services for automatic restart
87 |
88 | ## Quick Start
89 |
90 | ### Prerequisites
91 | - Ubuntu 22.04/24.04 LTS
92 | - Docker 24+ with Compose plugin
93 | - git, curl, wget
94 | - uv (installed via setup script)
95 |
96 | ### Local Development (Minimal)
97 | ```bash
98 | # Clone repository and demo app
99 | git clone https://github.com/mikeckennedy/devops-book-public /cluster-src
100 | cd /cluster-src/book/ch11-example-setup/containers/core-app/video-collector-docker/src
101 | git clone https://github.com/talkpython/htmx-python-course
102 |
103 | # Build and run just the application
104 | cd /cluster-src/book/ch11-example-setup/containers/base-images
105 | docker compose build
106 | cd ../core-app
107 | docker compose build
108 | docker compose up -d
109 |
110 | # Test application
111 | curl -I http://localhost:15000
112 | ```
113 |
114 | ### Production Setup
115 | **⚠️ WARNING**: Never run `setup-host-server.sh` directly! Execute it section by section, customizing for your environment.
116 |
117 | ```bash
118 | # Review and run setup script piece by piece
119 | less /cluster-src/book/ch11-example-setup/setup-host-server.sh
120 |
121 | # Key steps:
122 | # 1. Update system packages
123 | # 2. Install ZSH, Docker, utilities
124 | # 3. Create Docker network: docker network create -d bridge cluster-network --subnet=174.44.0.0/16
125 | # 4. Clone repositories and configure paths
126 | # 5. Set up systemd services for automatic startup
127 | ```
128 |
129 | ## Common Commands
130 |
131 | | Command | Purpose |
132 | |---------|---------|
133 | | **Building** | |
134 | | `docker compose build` | Build images in current directory |
135 | | `docker compose build --no-cache` | Force rebuild without cached layers |
136 | | **Container Management** | |
137 | | `docker compose up -d` | Start containers in background |
138 | | `docker compose down` | Stop and remove containers |
139 | | `docker compose restart` | Restart all services |
140 | | `docker ps` | List running containers |
141 | | **Logging & Debugging** | |
142 | | `docker compose logs -f -n 100` | Follow last 100 log lines |
143 | | `docker exec -it video-collector zsh` | Shell into app container |
144 | | `docker exec -it nginx bash` | Shell into NGINX container |
145 | | `tail -f /cluster-data/logs/video-collector/*.log` | Follow app logs |
146 | | **Deployment** | |
147 | | `deploy` | Full update and rebuild (alias) |
148 | | `dc up --build` | Rebuild and restart (dc = docker compose alias) |
149 | | **SSL Management** | |
150 | | `docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.com` | Request SSL certificate |
151 | | `docker compose run --rm certbot renew --webroot --webroot-path /var/www/certbot/` | Renew certificates |
152 | | **System Services** | |
153 | | `systemctl status core-app` | Check application service status |
154 | | `systemctl status web-servers` | Check NGINX service status |
155 | | `systemctl restart core-app` | Restart application service |
156 |
157 | ## Environment & Configuration
158 |
159 | ### Required Environment Variables
160 |
161 | Copy template and customize:
162 | ```bash
163 | cp book/ch11-example-setup/containers/web-servers/dot-env-template.sh .env
164 | ```
165 |
166 | Key variables to configure:
167 | ```bash
168 | # Core paths (create these directories first)
169 | APP_LOGS=/cluster-data/logs/video-collector
170 | NGINX_SITES_ENABLED=/cluster-data/nginx/sites-enabled
171 | NGINX_LOGS=/cluster-data/nginx/logs
172 | NGINX_STATIC=/cluster-data/nginx/static
173 |
174 | # SSL certificates (Let's Encrypt)
175 | NGINX_LETS_ENCRYPT_ETC=/cluster-data/nginx/letsencrypt-etc
176 | CERTBOT_WWW=/cluster-data/nginx/certbot/www
177 |
178 | # Domain configuration
179 | # Update video-collector.nginx with your actual domain
180 | ```
181 |
182 | ### Network Setup
183 | ```bash
184 | # Create external Docker network (run once)
185 | docker network create -d bridge cluster-network --subnet=174.44.0.0/16
186 |
187 | # Verify network exists
188 | docker network ls | grep cluster-network
189 | ```
190 |
191 | **⚠️ Security Note**: Never commit `.env` files to version control. Add `.env` to your `.gitignore`.
192 |
193 | ## Development Workflow
194 |
195 | ### Code Changes
196 | 1. **Application Code**: Edit files in `book/ch11-example-setup/containers/core-app/video-collector-docker/src/`
197 | 2. **Rebuild Core App**: `cd book/ch11-example-setup/containers/core-app && docker compose build`
198 | 3. **Restart**: `docker compose up -d`
199 |
200 | ### NGINX Configuration
201 | 1. **Edit Config**: Modify files in `/cluster-data/nginx/sites-enabled/`
202 | 2. **Reload NGINX**: `docker compose exec nginx nginx -s reload`
203 | 3. **No rebuild required** for configuration changes
204 |
205 | ### Full Deployment Pipeline
206 | ```bash
207 | # Use the deploy alias (defined in .zshrc)
208 | deploy
209 |
210 | # Or run manually:
211 | book/ch11-example-setup/scripts/update-source.sh
212 | book/ch11-example-setup/scripts/update-images.sh
213 | book/ch11-example-setup/scripts/build_containers.sh
214 | ```
215 |
216 | ### Hot Development (Local)
217 | For rapid iteration, mount your code as a volume:
218 | ```yaml
219 | # Add to core-app/compose.yaml temporarily
220 | volumes:
221 | - "${APP_LOGS}:/logs"
222 | - "./video-collector-docker/src:/app" # Mount source for hot reload
223 | ```
224 |
225 | ### Production Deployment
226 | - **Systemd Services**: Containers automatically start on boot
227 | - **Service Management**: Use `systemctl` commands for production control
228 | - **Log Persistence**: Logs are stored in `/cluster-data/logs/` on the host
229 |
230 | ## Helper Scripts Reference
231 |
232 | ### Setup Scripts
233 | | Script | Purpose | ⚠️ Notes |
234 | |--------|---------|----------|
235 | | `setup-host-server.sh` | Initial server configuration | **Never run directly!** Execute sections manually |
236 | | `create-docker-compose-service.sh` | Create systemd service for compose project | Run from compose directory |
237 |
238 | ### Deployment Scripts
239 | | Script | Purpose | Context |
240 | |--------|---------|---------|
241 | | `deploy.sh` | Complete update and rebuild pipeline | Production deployment |
242 | | `build_containers.sh` | Build all container images in sequence | Called by deploy.sh |
243 | | `update-images.sh` | Pull latest base images | Called by deploy.sh |
244 | | `update-source.sh` | Git pull latest source code | Called by deploy.sh |
245 |
246 | ### Useful Aliases (added to `.zshrc`)
247 | ```bash
248 | alias http='docker run -it --rm --net=host clue/httpie'
249 | alias glances='docker run --rm --name glances -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it docker.io/nicolargo/glances'
250 | alias deploy="/cluster-src/book/ch11-example-setup/scripts/deploy.sh"
251 | alias dc="docker compose"
252 | alias ls="pls" # Better ls alternative via uv
253 | ```
254 |
255 | ## Testing & Linting Guidelines
256 |
257 | ### Python Code Quality
258 | Configured in `ruff.toml`:
259 | ```toml
260 | line-length = 120
261 | format.quote-style = "single"
262 | lint.select = ["E", "F"] # Pyflakes errors and warnings
263 | target-version = "py311"
264 | ```
265 |
266 | ### Running Linting
267 | ```bash
268 | # Install ruff (if not already available)
269 | uv tool install ruff
270 |
271 | # Check code quality
272 | ruff check .
273 |
274 | # Auto-format code
275 | ruff format .
276 |
277 | # Check specific files
278 | ruff check book/ch08-docker-performance-tips/faster-docker-example/webapp/src/
279 | ```
280 |
281 | ### Testing
282 | - **Application Testing**: Use the demo Video Collector app to test the full stack
283 | - **HTTP Testing**: Use the `http` alias (HTTPie in Docker) for API testing
284 | - **Container Health**: Use Docker healthchecks where configured
285 |
286 | ### Pre-commit Hook (Optional)
287 | ```bash
288 | # .git/hooks/pre-commit
289 | #!/bin/bash
290 | ruff check . --exit-non-zero-on-fix
291 | ruff format . --check
292 | ```
293 |
294 | ## Contributing & Support
295 |
296 | ### Getting Help
297 | - **Book Questions**: [Talk Python in Production](https://talkpython.fm/books/python-in-production)
298 | - **Repository Issues**: Use GitHub Issues for bugs and feature requests
299 | - **General Discussion**: Use GitHub Discussions
300 | - **Training**: [Talk Python Training Courses](https://training.talkpython.fm/courses/all)
301 |
302 | ### Contributing
303 | 1. **Fork the Repository**: Create your own fork for testing changes
304 | 2. **Branch Strategy**: Use descriptive branch names (`feature/nginx-config`, `fix/docker-build`)
305 | 3. **Code Style**: Follow existing patterns and ruff configuration
306 | 4. **Testing**: Test your changes with the example application
307 | 5. **Documentation**: Update relevant sections of this WARP.md file
308 |
309 | ### Adding New Examples
310 | 1. **Chapter Structure**: Follow the existing pattern in `book/chXX-*/`
311 | 2. **Docker Best Practices**: Use multi-stage builds and layer caching
312 | 3. **Documentation**: Add code snippets to `galleries/code-gallery/README.md`
313 | 4. **Configuration**: Include template files for any required setup
314 |
315 | ### Pull Request Guidelines
316 | - **Clear Description**: Explain what problem you're solving
317 | - **Testing**: Include instructions for testing your changes
318 | - **Documentation**: Update WARP.md if you change workflows or add features
319 | - **Backwards Compatibility**: Consider existing users of the examples
320 |
321 | ---
322 |
323 | **Maintenance Note**: This WARP.md should be updated whenever Dockerfiles, scripts, or workflows change. The goal is to keep this as the single source of truth for working with this repository effectively.
324 |
--------------------------------------------------------------------------------
/galleries/figure-gallery/README.md:
--------------------------------------------------------------------------------
1 | # Image gallery
2 |
3 | This gallery contains high-quality versions of all the images in the book. Some readers have very high fidelity with the original: Apple Books or the Kindle App on iPad, the Kindle app on Android tablets, or even the Kindle app on your PC. Others, not so much. Devices like Kindle e-ink readers, Remarkable tablets, and other e-ink devices often have low pixel density and significantly reduce quality.
4 |
5 |
6 | **LICENSE**: Images in the image gallery are copyrighted and not available for reuse without express permission.
7 |
8 | ## Table of Contents
9 |
10 | * [Chapter 3: One Big Server Rather Than Many Small Ones](#chapter-03-gallery---one-big-server-rather-than-many-small-ones)
11 | * [Chapter 5: Running on Rust](#chapter-05-gallery---running-on-rust)
12 | * [Chapter 6: The Unexpected Benefits of Self-Hosting](#chapter-06-gallery---the-unexpected-benefits-of-self-hosting)
13 | * [Chapter 7: Visualizing Servers and Other Tools](#chapter-07-gallery---visualizing-servers-and-other-tools)
14 | * [Chapter 9: NGINX, Containers, and Let's Encrypt](#chapter-09-gallery---nginx-containers-and-lets-encrypt)
15 | * [Chapter 10: CDNs](#chapter-10-gallery---cdns)
16 | * [Chapter 11: Example Server Setup](#chapter-11-gallery---example-server-setup)
17 | * [Chapter 12: Static Sites and Hugo](#chapter-12-gallery---static-sites-and-hugo)
18 | * [Chapter 13: Picking a Python Web Framework](#chapter-13-gallery---picking-a-python-web-framework)
19 | * [Chapter 14: Sometimes You Should Build It Yourself](#chapter-14-gallery---sometimes-you-should-build-it-yourself)
20 | * [Chapter 15: Moving to Hetzner (a Retrospective)](#chapter-15-gallery---moving-to-hetzner-a-retrospective)
21 | * [Chapter 16: Opposite of Cloud-Native Is?](#chapter-16-gallery---opposite-of-cloud-native-is)
22 |
23 | ## Chapter 03 Gallery - One Big Server Rather Than Many Small Ones
24 |
25 | [](figures/03-01-workload-talkpython-apps.png)
26 | Figure 03-01: [Workload over time at our server](figures/03-01-workload-talkpython-apps.png)
27 |
28 |
29 |
30 | ----------------------------
31 |
32 | ## Chapter 05 Gallery - Running on Rust
33 |
34 | [](figures/05-01-architecture-tiers.png)
35 | Figure 05-01: [Visualization of Linux running Docker running our infrastructure.](figures/05-01-architecture-tiers.png)
36 |
37 |
38 | [](figures/05-02-granian-lots-of-granian.png)
39 | Figure 05-02: [View of processes on Docker host machine.](figures/05-02-granian-lots-of-granian.png)
40 |
41 |
42 |
43 | ----------------------------
44 |
45 | ## Chapter 06 Gallery - The Unexpected Benefits of Self-Hosting
46 |
47 | [](figures/06-01-server-tree.png)
48 | Figure 06-01: [Abstract visualization of our Docker infrastructure.](figures/06-01-server-tree.png)
49 |
50 |
51 | [](figures/06-02-events-chart.png)
52 | Figure 06-02: [Event graph in Umami.](figures/06-02-events-chart.png)
53 |
54 |
55 | [](figures/06-03-uptime-kuma.png)
56 | Figure 06-03: [Uptime Kuma home page.](figures/06-03-uptime-kuma.png)
57 |
58 |
59 | [](figures/06-04-status-talkpython.png)
60 | Figure 06-04: [Uptime Kuma status page for Talk Python.](figures/06-04-status-talkpython.png)
61 |
62 |
63 |
64 | ----------------------------
65 |
66 | ## Chapter 07 Gallery - Visualizing Servers and Other Tools
67 |
68 | [](figures/07-01-btop-dashboard.jpg)
69 | Figure 07-01: [btop dashboard updating real-time at Talk Python.](figures/07-01-btop-dashboard.jpg)
70 |
71 |
72 | [](figures/07-02-btop-options.jpg)
73 | Figure 07-02: [btop's graphical options screen, over SSH!](figures/07-02-btop-options.jpg)
74 |
75 |
76 | [](figures/07-03-glances.jpg)
77 | Figure 07-03: [Glances dashboard updating real-time at Talk Python.](figures/07-03-glances.jpg)
78 |
79 |
80 | [](figures/07-04-docker-cluster-monitor.jpg)
81 | Figure 07-04: [Docker Cluster Monitor of the containers at Talk Python.](figures/07-04-docker-cluster-monitor.jpg)
82 |
83 |
84 |
85 | ----------------------------
86 |
87 | ## Chapter 09 Gallery - NGINX, Containers, and Let's Encrypt
88 |
89 | [](figures/05-01-architecture-tiers.png)
90 | Figure 09-01: [Architectural view of NGINX and production app server on Docker + Linux](figures/05-01-architecture-tiers.png)
91 |
92 |
93 |
94 | ----------------------------
95 |
96 | ## Chapter 10 Gallery - CDNs
97 |
98 | [](figures/10-01-cdn-pops.jpg)
99 | Figure 10-01: [119 POPs (Points of Presence) for our CDN](figures/10-01-cdn-pops.jpg)
100 |
101 |
102 | [](figures/10-02-bunny-stats.png)
103 | Figure 10-02: [Talk Python's CDN traffic over two weeks](figures/10-02-bunny-stats.png)
104 |
105 |
106 |
107 | ----------------------------
108 |
109 | ## Chapter 11 Gallery - Example Server Setup
110 |
111 | [](figures/11-01-hetzner-projects.png)
112 | Figure 11-01: [Hetzner dashboard showing two projects](figures/11-01-hetzner-projects.png)
113 |
114 |
115 | [](figures/11-02-bash-basics.png)
116 | Figure 11-02: [Low capability default Bash shell](figures/11-02-bash-basics.png)
117 |
118 |
119 | [](figures/11-03-bash-basics.pngohmyzsh-completion.png)
120 | Figure 11-03: [High capability OhMyZSH shell](figures/11-03-bash-basics.pngohmyzsh-completion.png)
121 |
122 |
123 | [](figures/11-04-pls-listing.png)
124 | Figure 11-04: [Output from developer-friendly pls (ls alternative)](figures/11-04-pls-listing.png)
125 |
126 |
127 | [](figures/11-05-docker-layers.png)
128 | Figure 11-05: [Conceptualization of layered architecture with multiple Docker images](figures/11-05-docker-layers.png)
129 |
130 |
131 | [](figures/11-06-docker-compose-output-base-images.png)
132 | Figure 11-06: [Successful build using Docker Compose](figures/11-06-docker-compose-output-base-images.png)
133 |
134 |
135 | [](figures/11-07-video-collector.png)
136 | Figure 11-07: [Screenshot of Video Collector Flask app (our demo app)](figures/11-07-video-collector.png)
137 |
138 |
139 | [](figures/11-08-build-video-collector.png)
140 | Figure 11-08: [Successful Docker Compose build of our Flask app](figures/11-08-build-video-collector.png)
141 |
142 |
143 | [](figures/11-09-running-video-collector.png)
144 | Figure 11-09: [Launch the Flask app with Docker Compose](figures/11-09-running-video-collector.png)
145 |
146 |
147 | [](figures/11-10-service-status.png)
148 | Figure 11-10: [Checking on the new Docker Compose based service](figures/11-10-service-status.png)
149 |
150 |
151 | [](figures/11-11-nginx-up.png)
152 | Figure 11-11: [Launch NGINX with Docker Compose](figures/11-11-nginx-up.png)
153 |
154 |
155 | [](figures/11-07-video-collector.png)
156 | Figure 11-12: [Our Video Collector Flask app, running in Docker](figures/11-07-video-collector.png)
157 |
158 |
159 | [](figures/11-12-deploy-output.png)
160 | Figure 11-13: [Deploying our app end-to-end with a script](figures/11-12-deploy-output.png)
161 |
162 |
163 |
164 | ----------------------------
165 |
166 | ## Chapter 12 Gallery - Static Sites and Hugo
167 |
168 | [](figures/12-01-hugo.png)
169 | Figure 12-01: [Hugo static site generator](figures/12-01-hugo.png)
170 |
171 |
172 | [](figures/12-02-berkmansolutions-1.png)
173 | Figure 12-02: [Lextree from Bearkman Solutions, built entirely with Hugo](figures/12-02-berkmansolutions-1.png)
174 |
175 |
176 | [](figures/12-03-berkmansolutions-2.png)
177 | Figure 12-03: [Data driven pages, but from markdown and Hugo](figures/12-03-berkmansolutions-2.png)
178 |
179 |
180 | [](figures/12-04-berkmansolutions-3.png)
181 | Figure 12-04: [Embedded CSV spreadsheet tools with Hugo](figures/12-04-berkmansolutions-3.png)
182 |
183 |
184 | [](figures/12-05-mkennedy-codes.png)
185 | Figure 12-05: [Michael's personal website](figures/12-05-mkennedy-codes.png)
186 |
187 |
188 | [](figures/12-06-talk-python-blog.png)
189 | Figure 12-06: [Talk Python's official blog](figures/12-06-talk-python-blog.png)
190 |
191 |
192 |
193 | ----------------------------
194 |
195 | ## Chapter 13 Gallery - Picking a Python Web Framework
196 |
197 | [](figures/13-01-frameworks.png)
198 | Figure 13-01: [Popularity of Python web frameworks](figures/13-01-frameworks.png)
199 |
200 |
201 | [](figures/13-02-webapp-to-quart.png)
202 | Figure 13-02: [PR migrating Talk Python from Pyramid to Quart (sync only)](figures/13-02-webapp-to-quart.png)
203 |
204 |
205 | [](figures/13-03-quart-to-async.png)
206 | Figure 13-03: [PR migrating Talk Python from Pyramid to Quart (async step)](figures/13-03-quart-to-async.png)
207 |
208 |
209 |
210 | ----------------------------
211 |
212 | ## Chapter 14 Gallery - Sometimes You Should Build It Yourself
213 |
214 | [](figures/14-01-other-podcast.jpg)
215 | Figure 14-01: [Screenshot of Podbean hosting a podcast](figures/14-01-other-podcast.jpg)
216 |
217 |
218 | [](figures/14-02-us-podcast.jpg)
219 | Figure 14-02: [Talk Python's episode page circa 2024](figures/14-02-us-podcast.jpg)
220 |
221 |
222 | [](figures/14-03-courses.jpg)
223 | Figure 14-03: [Talk Python courses video player](figures/14-03-courses.jpg)
224 |
225 |
226 | [](figures/14-04-live-youtube.jpg)
227 | Figure 14-04: [YouTube live stream of Talk Python episode](figures/14-04-live-youtube.jpg)
228 |
229 |
230 | [](figures/14-05-live-site.jpg)
231 | Figure 14-05: [talkpython.fm in live streaming mode](figures/14-05-live-site.jpg)
232 |
233 |
234 | [](figures/14-06-twitter-share.jpg)
235 | Figure 14-06: [Social media post from episode featuring YouTube thumbnail](figures/14-06-twitter-share.jpg)
236 |
237 |
238 |
239 | ----------------------------
240 |
241 | ## Chapter 15 Gallery - Moving to Hetzner (a Retrospective)
242 |
243 | [](figures/15-01-hetzner-dashboard.png)
244 | Figure 15-01: [Cloud dashboard at Hetzner](figures/15-01-hetzner-dashboard.png)
245 |
246 |
247 | [](figures/15-02-hetzner-server.png)
248 | Figure 15-02: [Our server specifications](figures/15-02-hetzner-server.png)
249 |
250 |
251 | [](figures/15-03-bandwidth-and-cpu-compared.png)
252 | Figure 15-03: [Bandwidth and CPU compared between Hetzner and DigitalOcean](figures/15-03-bandwidth-and-cpu-compared.png)
253 |
254 |
255 |
256 | ----------------------------
257 |
258 | ## Chapter 16 Gallery - Opposite of Cloud-Native Is?
259 |
260 | [](figures/16-01-cloud-native-diagram-final.png)
261 | Figure 16-01: [Architectural diagram for a theoretical Cloud-native app](figures/16-01-cloud-native-diagram-final.png)
262 |
263 |
264 | [](figures/16-01-stack-native-to-scale-final.png)
265 | Figure 16-02: [Architectural diagram for our Flask app](figures/16-01-stack-native-to-scale-final.png)
266 |
267 |
268 | [](figures/16-01-stack-native-final.png)
269 | Figure 16-03: [Architectural diagram for our Flask app (close-up)](figures/16-01-stack-native-final.png)
270 |
271 |
272 |
273 | ----------------------------
274 |
275 |
276 | **LICENSE**: Images in the figure gallery are copyright and not available for reuse.
277 |
278 |
--------------------------------------------------------------------------------
/galleries/links-gallery/readme.md:
--------------------------------------------------------------------------------
1 | # Resources and Links Gallery
2 |
3 | This gallery contains all external links and resources referenced in the book. Links are organized by chapter for easy reference. Each link shows the text as it appears in the book along with the full URL.
4 |
5 | You can use this gallery to quickly find and visit any external resource mentioned throughout the book. Note that not all chapters have external links, so they may not appear in this gallery.
6 |
7 | ## Table of Contents
8 |
9 | * [Chapter 1: What we've tried](#chapter-01---what-weve-tried)
10 | * [Chapter 4: Docker, Docker, Docker](#chapter-04---docker-docker-docker)
11 | * [Chapter 5: Running on Rust](#chapter-05---running-on-rust)
12 | * [Chapter 6: The Unexpected Benefits of Self-Hosting](#chapter-06---the-unexpected-benefits-of-self-hosting)
13 | * [Chapter 7: Visualizing Servers and Other Tools](#chapter-07---visualizing-servers-and-other-tools)
14 | * [Chapter 8: Docker Performance Tips](#chapter-08---docker-performance-tips)
15 | * [Chapter 9: NGINX, Containers, and Let's Encrypt](#chapter-09---nginx-containers-and-lets-encrypt)
16 | * [Chapter 10: CDNs](#chapter-10---cdns)
17 | * [Chapter 11: Example Server Setup](#chapter-11---example-server-setup)
18 | * [Chapter 12: Static Sites and Hugo](#chapter-12---static-sites-and-hugo)
19 | * [Chapter 13: Picking a Python Web Framework](#chapter-13---picking-a-python-web-framework)
20 | * [Chapter 14: Sometimes You Should Build It Yourself](#chapter-14---sometimes-you-should-build-it-yourself)
21 | * [Chapter 15: Moving to Hetzner (a Retrospective)](#chapter-15---moving-to-hetzner-a-retrospective)
22 | * [Chapter 16: Opposite of Cloud-Native Is?](#chapter-16---opposite-of-cloud-native-is)
23 |
24 | ## Chapter 01 - What we've tried
25 |
26 | * **IIS** - [https://learn.microsoft.com/en-us/iis/get-started/introduction-to-iis/iis-web-server-overview](https://learn.microsoft.com/en-us/iis/get-started/introduction-to-iis/iis-web-server-overview)
27 | * **DigitalOcean** - [https://m.do.co/c/88e9531cb6aa](https://m.do.co/c/88e9531cb6aa)
28 | * **You're not Google, you're not Facebook, and you're not Netflix** - [https://programmerfriend.com/not-google/](https://programmerfriend.com/not-google/)
29 | * **Bringing Python to the Masses with Hosting and DevOps at PythonAnywhere** - [https://talkpython.fm/episodes/show/10/bringing-python-to-the-masses-with-hosting-and-devops-at-pythonanywhere](https://talkpython.fm/episodes/show/10/bringing-python-to-the-masses-with-hosting-and-devops-at-pythonanywhere)
30 | * **Talk Python** - [https://talkpython.fm](https://talkpython.fm)
31 | * **PythonAnywhere** - [https://www.pythonanywhere.com](https://www.pythonanywhere.com)
32 |
33 |
34 | ----------------------------
35 |
36 | ## Chapter 04 - Docker, Docker, Docker
37 |
38 | * **Migrating from AWS to Self-Hosting** - [https://ziglang.org/news/migrate-to-self-hosting/](https://ziglang.org/news/migrate-to-self-hosting/)
39 |
40 |
41 | ----------------------------
42 |
43 | ## Chapter 05 - Running on Rust
44 |
45 | * **Free-threaded Python** - [https://codspeed.io/blog/state-of-python-3-13-performance-free-threading](https://codspeed.io/blog/state-of-python-3-13-performance-free-threading)
46 | * **A web garden** - [https://docs.devexpress.com/AspNet/6874/common-concepts/web-farm-and-web-garden-support](https://docs.devexpress.com/AspNet/6874/common-concepts/web-farm-and-web-garden-support)
47 | * **Flask's** - [https://flask.palletsprojects.com/en/stable/deploying/](https://flask.palletsprojects.com/en/stable/deploying/)
48 | * **A performance table** - [https://github.com/emmett-framework/granian/blob/master/benchmarks/vs.md](https://github.com/emmett-framework/granian/blob/master/benchmarks/vs.md)
49 | * **hyper** - [https://github.com/hyperium/hyper](https://github.com/hyperium/hyper)
50 | * **Gunicorn** - [https://gunicorn.org/](https://gunicorn.org/)
51 | * **We Must Replace uWSGI With Something Else** - [https://mkennedy.codes/posts/we-must-replace-uwsgi-with-something-else-but-with-what/](https://mkennedy.codes/posts/we-must-replace-uwsgi-with-something-else-but-with-what/)
52 | * **setproctitle** - [https://pypi.org/project/setproctitle/](https://pypi.org/project/setproctitle/)
53 | * **uWSGI** - [https://uwsgi-docs.readthedocs.io/en/latest/](https://uwsgi-docs.readthedocs.io/en/latest/)
54 | * **Uvicorn** - [https://www.uvicorn.org/](https://www.uvicorn.org/)
55 |
56 |
57 | ----------------------------
58 |
59 | ## Chapter 06 - The Unexpected Benefits of Self-Hosting
60 |
61 | * **ArchiveBox** - [https://archivebox.io/](https://archivebox.io/)
62 | * **awesome-selfhosted.net** - [https://awesome-selfhosted.net/](https://awesome-selfhosted.net/)
63 | * **Cal.com** - [https://cal.com/](https://cal.com/)
64 | * **github.com/umami-software/umami** - [https://github.com/umami-software/umami](https://github.com/umami-software/umami)
65 | * **Hoarder.app** - [https://hoarder.app/](https://hoarder.app/)
66 | * **Such as Austria** - [https://noyb.eu/en/austrian-dsb-eu-us-data-transfers-google-analytics-illegal](https://noyb.eu/en/austrian-dsb-eu-us-data-transfers-google-analytics-illegal)
67 | * **Plausible.io** - [https://plausible.io/](https://plausible.io/)
68 | * **pypi.org/project/umami-analytics** - [https://pypi.org/project/umami-analytics/](https://pypi.org/project/umami-analytics/)
69 | * **Uptime Kuma** - [https://uptime.kuma.pet/](https://uptime.kuma.pet/)
70 | * **uptimekuma.talkpython.fm/status/talk-python** - [https://uptimekuma.talkpython.fm/status/talk-python](https://uptimekuma.talkpython.fm/status/talk-python)
71 | * **Rocket.chat** - [https://www.rocket.chat/](https://www.rocket.chat/)
72 |
73 |
74 | ----------------------------
75 |
76 | ## Chapter 07 - Visualizing Servers and Other Tools
77 |
78 | * **Uv installed** - [https://docs.astral.sh/uv/getting-started/installation/](https://docs.astral.sh/uv/getting-started/installation/)
79 | * **Figure gallery** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/galleries/figure-gallery](https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/galleries/figure-gallery)
80 | * **github.com/aristocratos/btop** - [https://github.com/aristocratos/btop](https://github.com/aristocratos/btop)
81 | * **github.com/mikeckennedy/dockerclustermon** - [https://github.com/mikeckennedy/dockerclustermon](https://github.com/mikeckennedy/dockerclustermon)
82 | * **github.com/nicolargo/glances** - [https://github.com/nicolargo/glances](https://github.com/nicolargo/glances)
83 | * **ohmyzsh** - [https://ohmyz.sh/](https://ohmyz.sh/)
84 |
85 |
86 | ----------------------------
87 |
88 | ## Chapter 08 - Docker Performance Tips
89 |
90 | * **uv** - [https://docs.astral.sh/uv/](https://docs.astral.sh/uv/)
91 | * **github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/book/ch08-docker-performance-tips/faster-docker-example** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/book/ch08-docker-performance-tips/faster-docker-example](https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/book/ch08-docker-performance-tips/faster-docker-example)
92 | * **The setup-uv website** - [https://github.com/astral-sh/setup-uv](https://github.com/astral-sh/setup-uv)
93 | * **So does Hynek** - [https://hynek.me/articles/docker-virtualenv/](https://hynek.me/articles/docker-virtualenv/)
94 |
95 |
96 | ----------------------------
97 |
98 | ## Chapter 09 - NGINX, Containers, and Let's Encrypt
99 |
100 | * **NGINX** - [https://NGINX.org/](https://NGINX.org/)
101 | * **Caddy** - [https://caddyserver.com/](https://caddyserver.com/)
102 | * **certbot.eff.org** - [https://certbot.eff.org/](https://certbot.eff.org/)
103 | * **Used by 33.8% of all public websites** - [https://w3techs.com/technologies/details/ws-nginx](https://w3techs.com/technologies/details/ws-nginx)
104 |
105 |
106 | ----------------------------
107 |
108 | ## Chapter 10 - CDNs
109 |
110 | * **Bunny.net CDN service** - [https://bunny.net?ref=b4f3tqcyae](https://bunny.net?ref=b4f3tqcyae)
111 | * **CDNs Made Easy: Accelerate Your Python Web Apps** - [https://training.talkpython.fm/courses/fast-python-web-apps-with-cdn](https://training.talkpython.fm/courses/fast-python-web-apps-with-cdn)
112 |
113 |
114 | ----------------------------
115 |
116 | ## Chapter 11 - Example Server Setup
117 |
118 | * **accounts.hetzner.com/signUp** - [https://accounts.hetzner.com/signUp](https://accounts.hetzner.com/signUp)
119 | * **Safe is not 100% foolproof, is it** - [https://arstechnica.com/security/2024/03/backdoor-found-in-widely-used-linux-utility-breaks-encrypted-ssh-connections/](https://arstechnica.com/security/2024/03/backdoor-found-in-widely-used-linux-utility-breaks-encrypted-ssh-connections/)
120 | * **The steps suggested by Docker themselves** - [https://docs.docker.com/engine/install/ubuntu/](https://docs.docker.com/engine/install/ubuntu/)
121 | * **A fun working Flask and HTMX application** - [https://github.com/talkpython/htmx-python-course](https://github.com/talkpython/htmx-python-course)
122 | * **Chapter 11: Example Setup** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/book/ch11-example-setup](https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/book/ch11-example-setup)
123 | * **Figure gallery** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/galleries/figure-gallery](https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/galleries/figure-gallery)
124 | * **github.com/mikeckennedy/talk-python-in-production-devops-book** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book](https://github.com/mikeckennedy/talk-python-in-production-devops-book)
125 | * **github.com/mikeckennedy/talk-python-in-production-devops-book/blob/main/book/ch11-example-setup/setup-host-server.sh** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book/blob/main/book/ch11-example-setup/setup-host-server.sh](https://github.com/mikeckennedy/talk-python-in-production-devops-book/blob/main/book/ch11-example-setup/setup-host-server.sh)
126 | * **Granian's documentation** - [https://github.com/emmett-framework/granian?tab=readme-ov-file#options](https://github.com/emmett-framework/granian?tab=readme-ov-file#options)
127 | * **pls** - [https://github.com/pls-rs/pls](https://github.com/pls-rs/pls)
128 | * **The code gallery** - [https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/galleries/code-gallery](https://github.com/mikeckennedy/talk-python-in-production-devops-book/tree/main/galleries/code-gallery)
129 | * **HTTPie** - [https://httpie.io/](https://httpie.io/)
130 | * **Hynek Schlawack** - [https://hynek.me/](https://hynek.me/)
131 | * **OhMyZSH** - [https://ohmyz.sh](https://ohmyz.sh)
132 | * **orbstack.dev** - [https://orbstack.dev/](https://orbstack.dev/)
133 | * **Create a systemd service for your docker** - [https://techoverflow.net/2020/10/24/create-a-systemd-service-for-your-docker-compose-project-in-10-seconds/](https://techoverflow.net/2020/10/24/create-a-systemd-service-for-your-docker-compose-project-in-10-seconds/)
134 | * **HTMX + Flask: Modern Python Web Apps, Hold the JavaScript course** - [https://training.talkpython.fm/courses/htmx-flask-modern-python-web-apps-hold-the-javascript](https://training.talkpython.fm/courses/htmx-flask-modern-python-web-apps-hold-the-javascript)
135 | * **The DigitalOcean documentation** - [https://www.digitalocean.com/community/tutorials/how-to-create-ssh-keys-with-openssh-on-macos-or-linux](https://www.digitalocean.com/community/tutorials/how-to-create-ssh-keys-with-openssh-on-macos-or-linux)
136 | * **docker.com/products/docker-desktop** - [https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/)
137 | * **A nerd font** - [https://www.nerdfonts.com/](https://www.nerdfonts.com/)
138 |
139 |
140 | ----------------------------
141 |
142 | ## Chapter 12 - Static Sites and Hugo
143 |
144 | * **Hugo** - [https://gohugo.io/](https://gohugo.io/)
145 | * **Michael's personal website** - [https://mkennedy.codes/](https://mkennedy.codes/)
146 | * **Pagefind** - [https://pagefind.app/](https://pagefind.app/)
147 | * **Have a look at https://talkpython.fm/sitemap.xml** - [https://talkpython.fm/sitemap.xml](https://talkpython.fm/sitemap.xml)
148 | * **Talk Python Blog** - [https://talkpython.fm/blog/](https://talkpython.fm/blog/)
149 | * **Talk Python in Production book** - [https://talkpython.fm/books/python-in-production](https://talkpython.fm/books/python-in-production)
150 | * **Uptime Kuma** - [https://uptime.kuma.pet/](https://uptime.kuma.pet/)
151 | * **berkmansolutions.com** - [https://www.berkmansolutions.com/](https://www.berkmansolutions.com/)
152 |
153 |
154 | ----------------------------
155 |
156 | ## Chapter 13 - Picking a Python Web Framework
157 |
158 | * **Django Girls** - [https://djangogirls.org/en/](https://djangogirls.org/en/)
159 | * **Free-threaded Python** - [https://docs.python.org/3/howto/free-threading-python.html](https://docs.python.org/3/howto/free-threading-python.html)
160 | * **Blueprint concept of Flask** - [https://flask.palletsprojects.com/en/stable/blueprints/](https://flask.palletsprojects.com/en/stable/blueprints/)
161 | * **Flask documentation about async and await** - [https://flask.palletsprojects.com/en/stable/async-await/](https://flask.palletsprojects.com/en/stable/async-await/)
162 | * **Chameleon-Flask** - [https://github.com/mikeckennedy/chameleon-flask](https://github.com/mikeckennedy/chameleon-flask)
163 | * **Quart framework** - [https://github.com/pallets/quart](https://github.com/pallets/quart)
164 | * **Hugo** - [https://gohugo.io/](https://gohugo.io/)
165 | * **Awesome locust.io framework** - [https://locust.io/](https://locust.io/)
166 | * **Python Developer Survey** - [https://lp.jetbrains.com/python-developers-survey-2023/](https://lp.jetbrains.com/python-developers-survey-2023/)
167 | * **Quart** - [https://quart.palletsprojects.com](https://quart.palletsprojects.com)
168 | * **Interviewed the team** - [https://talkpython.fm/episodes/show/433/litestar-effortlessly-build-performant-apis](https://talkpython.fm/episodes/show/433/litestar-effortlessly-build-performant-apis)
169 | * **State of Flask and Pallets in 2024** - [https://talkpython.fm/episodes/show/472/state-of-flask-and-pallets-in-2024](https://talkpython.fm/episodes/show/472/state-of-flask-and-pallets-in-2024)
170 | * **talkpython.fm** - [https://talkpython.fm/episodes/all](https://talkpython.fm/episodes/all)
171 | * **talkpython.fm/sitemap.xml** - [https://talkpython.fm/sitemap.xml](https://talkpython.fm/sitemap.xml)
172 | * **Their own page** - [https://talkpython.fm/guests](https://talkpython.fm/guests)
173 | * **How to build awesome HTML page-based sites with FastAPI** - [https://training.talkpython.fm/courses/full-html-web-applications-with-fastapi](https://training.talkpython.fm/courses/full-html-web-applications-with-fastapi)
174 | * **Pyramid** - [https://trypyramid.com/](https://trypyramid.com/)
175 |
176 |
177 | ----------------------------
178 |
179 | ## Chapter 14 - Sometimes You Should Build It Yourself
180 |
181 | * **Across CDNs** - [https://bunny.net?ref=b4f3tqcyae](https://bunny.net?ref=b4f3tqcyae)
182 | * **Not Invented Here syndrome** - [https://en.wikipedia.org/wiki/Not_invented_here](https://en.wikipedia.org/wiki/Not_invented_here)
183 | * **Look at PyPI** - [https://pypi.org/search/?q=postgres&o=](https://pypi.org/search/?q=postgres&o=)
184 | * **And I get to learn HTMX** - [https://training.talkpython.fm/courses/htmx-flask-modern-python-web-apps-hold-the-javascript](https://training.talkpython.fm/courses/htmx-flask-modern-python-web-apps-hold-the-javascript)
185 | * **Courses at Talk Python** - [https://training.talkpython.fm/courses/all](https://training.talkpython.fm/courses/all)
186 | * **Stream Deck** - [https://www.elgato.com/en/stream-deck](https://www.elgato.com/en/stream-deck)
187 | * **Paddle** - [https://www.paddle.com](https://www.paddle.com)
188 | * **Live audience on YouTube** - [https://www.youtube.com/@talkpython](https://www.youtube.com/@talkpython)
189 |
190 |
191 | ----------------------------
192 |
193 | ## Chapter 15 - Moving to Hetzner (a Retrospective)
194 |
195 | * **lgaborini/benchmarkbash** - [https://hub.docker.com/r/lgaborini/benchmarkbash/](https://hub.docker.com/r/lgaborini/benchmarkbash/)
196 | * **moutten/speedtest-cli** - [https://hub.docker.com/r/moutten/speedtest-cli](https://hub.docker.com/r/moutten/speedtest-cli)
197 | * **DigitalOcean** - [https://m.do.co/c/88e9531cb6aa](https://m.do.co/c/88e9531cb6aa)
198 | * **Hetzner opened two US-based data centers** - [https://www.hetzner.com/presse-berichte/2021/0/166513/](https://www.hetzner.com/presse-berichte/2021/0/166513/)
199 | * **Their cloud-VM pricing and SKU page** - [https://www.hetzner.com/cloud/](https://www.hetzner.com/cloud/)
200 | * **Hetzner run** - [https://www.speedtest.net/result/c/29d9791d-920c-457d-8454-350f56475293](https://www.speedtest.net/result/c/29d9791d-920c-457d-8454-350f56475293)
201 |
202 |
203 | ----------------------------
204 |
205 | ## Chapter 16 - Opposite of Cloud-Native Is?
206 |
207 | * **Coolify** - [https://coolify.io](https://coolify.io)
208 | * **Hetzner** - [https://hetzner.com/cloud/](https://hetzner.com/cloud/)
209 | * **Merchants of complexity** - [https://world.hey.com/dhh/merchants-of-complexity-4851301b](https://world.hey.com/dhh/merchants-of-complexity-4851301b)
210 | * **Cloud Native Patterns** - [https://www.manning.com/books/cloud-native-patterns](https://www.manning.com/books/cloud-native-patterns)
211 |
212 |
213 | ----------------------------
214 |
215 |
--------------------------------------------------------------------------------
/galleries/code-gallery/README.md:
--------------------------------------------------------------------------------
1 | # Code Gallery
2 |
3 | This gallery contains all the code blocks from the book organized by chapter. Each code block is listed with its language and can be easily copied and used. Note that the first few chapters don't have any code and thus do not appear here.
4 |
5 | ## Table of Contents
6 |
7 | * [Chapter 5: Running on Rust](#chapter-5-running-on-rust)
8 | * [Chapter 6: The Unexpected Benefits of Self-Hosting](#chapter-6-the-unexpected-benefits-of-self-hosting)
9 | * [Chapter 7: Visualizing Servers and Other Tools](#chapter-7-visualizing-servers-and-other-tools)
10 | * [Chapter 8: Docker Performance Tips](#chapter-8-docker-performance-tips)
11 | * [Chapter 9: NGINX, Containers, and Let's Encrypt](#chapter-9-nginx-containers-and-lets-encrypt)
12 | * [Chapter 10: CDNs](#chapter-10-cdns)
13 | * [Chapter 11: Example Server Setup](#chapter-11-example-server-setup)
14 | * [Chapter 12: Static Sites and Hugo](#chapter-12-static-sites-and-hugo)
15 | * [Chapter 13: Picking a Python Web Framework](#chapter-13-picking-a-python-web-framework)
16 |
17 | ## Chapter 5: Running on Rust
18 |
19 | ### Code block 05-01 - Linux Shell
20 |
21 | ```bash
22 | # Flask when executing app.run()
23 |
24 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
25 | ```
26 |
27 | ### Code block 05-02 - Linux Shell
28 |
29 | ```bash
30 | # Command to run talkpython.fm in a production app server.
31 |
32 | granian talkpython.quart_app:app \
33 | --host 0.0.0.0 --port 8801 \
34 | --interface asgi \
35 | --no-ws
36 | --workers 3 \
37 | --runtime-mode st \
38 | --loop uvloop \
39 | --workers-lifetime 43200 --respawn-interval 30 \
40 | --process-name "granian-talkpython" \
41 | --log --log-level info
42 | ```
43 |
44 | ### Code block 05-03 - Docker
45 |
46 | ```dockerfile
47 | # Command to run talkpython.fm in
48 | # a Docker container.
49 |
50 | ENTRYPOINT [ \
51 | "/venv/bin/granian",\
52 | "talkpython.quart_app:app", \
53 | "--host","0.0.0.0", \
54 | "--port","8801", \
55 | "--interface","asgi", \
56 | "--no-ws", \
57 | "--workers","3", \
58 | "--threading-mode", "workers", \
59 | "--loop","uvloop", \
60 | "--log-level","info",\
61 | "--log", \
62 | "--workers-lifetime", "10800", \
63 | "--respawn-interval", "30", \
64 | "--process-name", "granian-talkpython" \
65 | ]
66 | ```
67 |
68 |
69 | ## Chapter 6: The Unexpected Benefits of Self-Hosting
70 |
71 | ### Code block 06-01 - Linux Shell
72 |
73 | ```bash
74 | # Create a persistent volume outside lifetime of containers.
75 | docker volume create umami-volume
76 | ```
77 |
78 | ### Code block 06-02 - Docker Compose
79 |
80 | ```yaml
81 | # Modified compose.yaml file to use external data volume.
82 |
83 | umami_db:
84 | # ...
85 | volumes:
86 | - umami-volume:/var/lib/postgresql/data
87 |
88 | volumes:
89 | umami-volume:
90 | name: umami-volume
91 | external: true
92 | ```
93 |
94 | ### Code block 06-03 - Linux Shell
95 |
96 | ```bash
97 | # In the Umami folder with the docker-compose.yml file:
98 | docker compose up
99 | ```
100 |
101 | ### Code block 06-04 - Docker Compose
102 |
103 | ```yaml
104 | # Compose file defining Uptime Kuma config.
105 |
106 | services:
107 | uptime-kuma:
108 | image: louislam/uptime-kuma:1
109 | volumes:
110 | - ./data:/app/data
111 | ports:
112 | - 3001:3001
113 | restart: unless-stopped
114 | ```
115 |
116 | ### Code block 06-05 - Linux Shell
117 |
118 | ```bash
119 | # Recommended external data for Uptime Kuma.
120 | docker volume create kuma-volume
121 | ```
122 |
123 | ### Code block 06-06 - Docker Compose
124 |
125 | ```yaml
126 | # Uptime Kuma Docker Compose config with external volume.
127 |
128 | services:
129 | uptime-kuma:
130 | image: louislam/uptime-kuma:1
131 | # Add this to make your life easier too
132 | container_name: uptime-kuma
133 | volumes:
134 | - kuma-volume:/app/data
135 | ports:
136 | - 3001:3001
137 | restart: unless-stopped
138 |
139 | volumes:
140 | kuma-volume:
141 | external: true
142 | ```
143 |
144 |
145 | ## Chapter 7: Visualizing Servers and Other Tools
146 |
147 | ### Code block 07-01 - Linux Shell
148 |
149 | ```bash
150 | apt install btop
151 | ```
152 |
153 | ### Code block 07-02 - Linux Shell
154 |
155 | ```bash
156 | # Download and install Glances utility using Docker isolation.
157 |
158 | docker pull nicolargo/glances:latest-full
159 |
160 | docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro -v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro --pid host --network host -it nicolargo/glances:latest-full
161 | ```
162 |
163 | ### Code block 07-03 - Linux Shell
164 |
165 | ```bash
166 | # Alias for Glances to make it super easy to run.
167 |
168 | # Add these three aliases in your .zshrc / .bashrc
169 | # file on the host server:
170 |
171 | alias update_glances="docker pull nicolargo/glances:latest-full"
172 |
173 | alias run_glances="docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro -v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro --pid host --network host -it nicolargo/glances:latest-full"
174 |
175 | alias glances="update_glances && run_glances"
176 | ```
177 |
178 | ### Code block 07-04 - Linux Shell
179 |
180 | ```bash
181 | # Installing Docker Cluster Monitor via uv.
182 |
183 | uv tool install dockerclustermon
184 | ```
185 |
186 | ### Code block 07-05 - Linux Shell
187 |
188 | ```bash
189 | # Installing Docker Cluster Monitor via uv.
190 |
191 | pipx install dockerclustermon
192 | ```
193 |
194 | ### Code block 07-06 - Linux Shell
195 |
196 | ```bash
197 | # Monitor Docker cluster at server SERVERNAME
198 |
199 | dockerstatus SERVERNAME
200 | ```
201 |
202 | ### Code block 07-07 - Linux Shell
203 |
204 | ```bash
205 | # Log into NGINX's running Docker container (starting Bash).
206 |
207 | docker exec -it nginx bash
208 | ```
209 |
210 | ### Code block 07-08 - Linux Shell
211 |
212 | ```bash
213 | # Log into running app server in Docker container (starting OhMyZSH).
214 |
215 | docker exec -it talkpython zsh
216 | (venv) ➜ /app
217 | ```
218 |
219 | ### Code block 07-09 - Linux Shell
220 |
221 | ```bash
222 | # .zshrc file: Set up OhMyZSH and
223 | # activate Python's venv on login.
224 |
225 | export ZSH="/root/.oh-my-zsh"
226 | ZSH_THEME="robbyrussell"
227 | plugins=()
228 | source $ZSH/oh-my-zsh.sh
229 |
230 | source /venv/bin/activate
231 | ```
232 |
233 | ### Code block 07-10 - Docker
234 |
235 | ```dockerfile
236 | # Docker command to install ZSH and set up OhMyZSH.
237 |
238 | # Uses "robbyrussell" theme (original Oh My Zsh theme), with no plugins
239 | RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.0/zsh-in-docker.sh)" -- -t robbyrussell
240 | ```
241 |
242 | ### Code block 07-11 - Linux Shell
243 |
244 | ```bash
245 | # Basic tail command, show prior 100 and follow new entries.
246 | tail -n 100 -f /logs/app.log
247 | ```
248 |
249 | ### Code block 07-12 - Docker Compose
250 |
251 | ```yaml
252 | # Docker Compose config to make logs persistent on host and "tailable."
253 |
254 | services:
255 | talkpython:
256 | image: talkpython
257 | # ...
258 | volumes:
259 | # Env variable TALKPYTHON_LOGS maps to a local folder.
260 | - "${TALKPYTHON_LOGS}:/logs"
261 | ```
262 |
263 | ### Code block 07-13 - Linux Shell
264 |
265 | ```bash
266 | # Tail the log and follow it for Talk Python's app server.
267 |
268 | tail -n 500 -f /cluster/logs/talkpython/request-log.log
269 | ```
270 |
271 |
272 | ## Chapter 8: Docker Performance Tips
273 |
274 | ### Code block 08-01 - Docker
275 |
276 | ```dockerfile
277 | # Simple Dockerfile example to illustrate layers in Docker build.
278 |
279 | FROM ubuntu:latest
280 |
281 | RUN mkdir /app
282 | COPY ./files /app
283 |
284 | RUN apt update
285 | RUN apt upgrade -y
286 |
287 | ENTRYPOINT [ ... your startup command here ... ]
288 | ```
289 |
290 | ### Code block 08-02 - Docker
291 |
292 | ```dockerfile
293 | # Reorder independent commands for faster rebuilds.
294 |
295 | FROM ubuntu:latest
296 |
297 | # Move ahead
298 | RUN apt update
299 | RUN apt upgrade -y
300 |
301 | # Move later
302 | RUN mkdir /app
303 | COPY ./files /app
304 |
305 | ENTRYPOINT [ ... your startup command here ... ]
306 | ```
307 |
308 | ### Code block 08-03 - Docker
309 |
310 | ```dockerfile
311 | # A docker file for a basic Flask application.
312 |
313 | FROM ubuntu:latest
314 |
315 | # Move ahead
316 | RUN apt update
317 | RUN apt upgrade -y
318 |
319 | # Magically install Python
320 | # We'll talk about how to do this next.
321 |
322 | # ############ FOCUS HERE ##############
323 | RUN mkdir /app
324 | WORKDIR "/app"
325 |
326 | COPY ./src/ /app
327 | RUN python -m venv /app/venv
328 | RUN /app/venv/bin/pip install -r requirements.txt
329 | # ######### UNTIL HERE #################
330 |
331 | ENTRYPOINT [ ... your startup command here ... ]
332 | ```
333 |
334 | ### Code block 08-04 - Docker
335 |
336 | ```dockerfile
337 | # Splitting the requirements file copy and the source files copy.
338 | ...
339 |
340 | # ############ FOCUS HERE ##############
341 | # Break out the copying of the requirements file and
342 | # the install of the dependencies:
343 | COPY ./src/requirements.txt /app
344 | RUN python -m venv /app/venv
345 | RUN /app/venv/bin/pip install -r requirements.txt
346 |
347 | COPY ./src/ /app
348 | # ######### UNTIL HERE #################
349 | ...
350 | ```
351 |
352 | ### Code block 08-05 - Docker
353 |
354 | ```dockerfile
355 | # Cache the venv by moving it before copied files.
356 | ...
357 |
358 | # ############ FOCUS HERE ##############
359 | # Move this ahead of any of our file changes.
360 | RUN python -m venv /app/venv
361 |
362 | COPY ./src/requirements.txt /app
363 | RUN /app/venv/bin/pip install -r requirements.txt
364 |
365 | COPY ./src/ /app
366 | # ######### UNTIL HERE #################
367 | ...
368 | ```
369 |
370 | ### Code block 08-06 - Docker
371 |
372 | ```dockerfile
373 | # Add uv tooling and use it to install requirements.
374 |
375 | FROM ubuntu:latest
376 |
377 | # ...
378 |
379 | # Set some paths making it easier to run uv and python
380 | ENV PATH=/venv/bin:$PATH
381 | ENV PATH="/root/.local/bin/:$PATH"
382 |
383 | # ############ FOCUS HERE ##############
384 |
385 | # Install uv
386 | RUN curl -LsSf https://astral.sh/uv/install.sh | sh
387 |
388 | # Create the venv and download Python 3.13
389 | RUN uv venv --python 3.13 /app/venv
390 |
391 | COPY ./src/requirements.txt /app
392 | # Use uv now rather than pip
393 | RUN uv pip install -r requirements.txt
394 |
395 | # ######### UNTIL HERE #################
396 |
397 | COPY ./src/ /app
398 |
399 | # ...
400 | ```
401 |
402 | ### Code block 08-07 - Docker
403 |
404 | ```dockerfile
405 | # mount command persists uv cache across builds (even rebuilds).
406 |
407 | FROM ubuntu:latest
408 |
409 | # ...
410 |
411 | # Use uv now rather than pip
412 | RUN --mount=type=cache,target=/root/.cache uv pip install -r requirements.txt
413 |
414 | # ...
415 | ```
416 |
417 | ### Code block 08-08 - Docker
418 |
419 | ```dockerfile
420 | # Going faster by ignoring files.
421 | # .dockerignore - located next to Dockerfile
422 |
423 | .git
424 | **/.git
425 | **/.github
426 | **/.fleet
427 | **/.vscode
428 | **/.idea
429 | **/src/logos # Suppose in the repo but not part of the app
430 | **/src/docs # Same as logos
431 | # Don't let dev requirements cause rebuilds.
432 | **/requirements-development.txt
433 | ```
434 |
435 |
436 | ## Chapter 9: NGINX, Containers, and Let's Encrypt
437 |
438 | ### Code block 09-01 - NGINX
439 |
440 | ```nginx
441 | # A very basic talkpython.fm NGINX file.
442 |
443 | server {
444 | listen 80;
445 | server_name talkpython.fm;
446 | charset utf-8;
447 |
448 | location /static {
449 | gzip on;
450 | gzip_comp_level 6;
451 | gzip_min_length 1100;
452 | gzip_buffers 16 256k;
453 | gzip_proxied any;
454 | gzip_types
455 | text/plain
456 | text/xml
457 | text/css
458 | application/javascript
459 | application/json
460 | application/xml
461 | application/rss+xml;
462 |
463 | alias /webapp/static/talkpython/talkpython.fm;
464 | expires 365d;
465 | }
466 |
467 | location /.well-known/acme-challenge/ { root /var/www/certbot; }
468 |
469 | location / { try_files $uri @talk_python_app; }
470 |
471 | location @talk_python_app {
472 | include uwsgi_params;
473 | proxy_pass http://180.0.0.101:7195;
474 | proxy_set_header Host $host;
475 | proxy_set_header X-Real-IP $remote_addr;
476 | proxy_set_header X-Forwarded-Protocol $scheme;
477 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
478 | proxy_set_header X-Forwarded-Host $host;
479 | }
480 | }
481 | ```
482 |
483 | ### Code block 09-02 - Docker Compose
484 |
485 | ```yaml
486 | # Docker Compose file for connecting NGINX and CertBot.
487 |
488 | services:
489 |
490 | nginx:
491 | image: nginx:latest
492 | container_name: nginx
493 | restart: unless-stopped
494 | ports:
495 | - "80:80"
496 | - "443:443"
497 | volumes:
498 | - "${NGINX_SITES_ENABLED}:/etc/NGINX/sites-enabled"
499 | - "${CERTBOT_WWW}:/var/www/certbot/"
500 | - "${NGINX_LETS_ENCRYPT_ETC}:/etc/letsencrypt/"
501 | networks:
502 | cluster-net:
503 | ipv4_address: 180.0.0.55
504 |
505 | certbot:
506 | image: certbot/certbot:latest
507 | container_name: certbot
508 | volumes:
509 | - "${CERTBOT_WWW}:/var/www/certbot/"
510 | - "${NGINX_LETS_ENCRYPT_ETC}:/etc/letsencrypt/:rw"
511 | networks:
512 | webapp-network:
513 |
514 | # Created one time on host machine setup via:
515 | # docker network create -d bridge webapp --subnet=180.0.0.0/16
516 | networks:
517 | webapp-network:
518 | name: webapp
519 | external: true
520 | ```
521 |
522 | ### Code block 09-03 - Linux Shell
523 |
524 | ```bash
525 | # Command to run certbot within Docker Compose config.
526 |
527 | docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d talkpython.fm
528 | ```
529 |
530 | ### Code block 09-04 - NGINX
531 |
532 | ```nginx
533 | # Updated NGINX config with newly created certificate files.
534 |
535 | server {
536 | server_name talkpython.fm;
537 | charset utf-8;
538 |
539 | # Add this section to listen on HTTPS port, enable HTTP2, and use the certs.
540 | listen 443 ssl;
541 | http2 on;
542 | ssl_certificate /etc/letsencrypt/live/talkpython.fm/fullchain.pem;
543 | ssl_certificate_key /etc/letsencrypt/live/talkpython.fm/privkey.pem;
544 | include /etc/letsencrypt/options-ssl-NGINX.conf;
545 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
546 |
547 | location /static {
548 | gzip on;
549 | gzip_comp_level 6;
550 | gzip_min_length 1100;
551 | gzip_buffers 16 256k;
552 | gzip_proxied any;
553 | gzip_types
554 | text/plain
555 | text/xml
556 | text/css
557 | application/javascript
558 | application/json
559 | application/xml
560 | application/rss+xml;
561 |
562 | alias /webapp/static/talkpython/talkpython.fm;
563 | expires 365d;
564 | }
565 |
566 | location /.well-known/acme-challenge/ { root /var/www/certbot; }
567 |
568 | location / {
569 | try_files $uri @talk_python_app;
570 | }
571 |
572 | location @talk_python_app {
573 | include uwsgi_params;
574 | proxy_pass http://180.0.0.101:7195;
575 | proxy_set_header Host $host;
576 | proxy_set_header X-Real-IP $remote_addr;
577 | proxy_set_header X-Forwarded-Protocol $scheme;
578 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
579 | proxy_set_header X-Forwarded-Host $host;
580 | }
581 | }
582 |
583 | # Add an automatic redirect from HTTP (80) to HTTPS.
584 | server {
585 | if ($host = talkpython.fm) {
586 | return 301 https://$host$request_uri;
587 | }
588 |
589 | listen 80;
590 | server_name talkpython.fm;
591 | return 404;
592 | }
593 | ```
594 |
595 | ### Code block 09-05 - Linux Shell
596 |
597 | ```bash
598 | # Command to run CertBot to renew all domains.
599 |
600 | docker compose run --rm certbot renew --webroot --webroot-path /var/www/certbot/
601 | ```
602 |
603 | ### Code block 09-06 - Linux Shell
604 |
605 | ```bash
606 | # Output from renewing all domains.
607 |
608 | Saving debug log to /var/log/letsencrypt/letsencrypt.log
609 |
610 | - - - - - - - - - - - - - - - - - - - - - - - - - - - -
611 | Processing /etc/letsencrypt/renewal/talkpython.fm.conf
612 | - - - - - - - - - - - - - - - - - - - - - - - - - - - -
613 | Certificate not yet due for renewal
614 |
615 | The following certificates are not due for renewal yet:
616 | /etc/letsencrypt/live/talkpython.fm/fullchain.pem expires on 2025-01-31 (skipped)
617 | # ... many more redacted, also not renewed. :)
618 | ```
619 |
620 |
621 | ## Chapter 10: CDNs
622 |
623 | ### Code block 10-01 - HTML
624 |
625 | ```html
626 |
627 |
628 | https://cdn-podcast.talkpython.fm/static/css/site.css?cache_id=9b9f84
629 | ```
630 |
631 | ### Code block 10-02 - HTML
632 |
633 | ```html
634 |
635 |
636 | /static/css/site.css?cache_id=9b9f84
637 | ```
638 |
639 | ### Code block 10-03 - HTML
640 |
641 | ```html
642 |
643 |
644 | https://download-cdn.talkpython.fm/podcasts/talkpython/487-building-rust-extensions-for-python.mp3
645 | ```
646 |
647 |
648 | ## Chapter 11: Example Server Setup
649 |
650 | ### Code block 11-01 - Hosts file
651 |
652 | ```bash
653 | # Entry in /etc/hosts or C:\Windows\System32\drivers\etc\hosts
654 | 20.21.22.23 pyprod-host
655 | ```
656 |
657 | ### Code block 11-02 - Linux Shell
658 |
659 | ```bash
660 | # Connect to pyprod-host using SSH (needs hosts entry).
661 | ssh root@pyprod-host
662 | ```
663 |
664 | ### Code block 11-03 - Linux Shell
665 |
666 | ```bash
667 | # Welcome screen at pyprod-host.
668 |
669 | Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-49-generic x86_64)
670 |
671 | * Documentation: https://help.ubuntu.com
672 | * Management: https://landscape.canonical.com
673 | * Support: https://ubuntu.com/pro
674 |
675 | System information
676 |
677 | System load: 0.02 Processes: 138
678 | Usage of /: 24.8% of 37.23GB Users logged in: 0
679 | Memory usage: 28% IPv4 address for eth0: 20.21.22.23
680 | Swap usage: 0%
681 |
682 | * Strictly confined Kubernetes makes edge and
683 | IoT secure. Learn how MicroK8s just raised
684 | the bar for easy, resilient and secure K8s
685 | cluster deployment.
686 |
687 | https://ubuntu.com/engage/secure-kubernetes-at-the-edge
688 |
689 | Expanded Security Maintenance for Applications is not enabled.
690 |
691 | 180 updates can be applied immediately.
692 |
693 | Enable ESM Apps to receive additional future security updates.
694 | See https://ubuntu.com/esm or run: sudo pro status
695 | ```
696 |
697 | ### Code block 11-04 - Linux Shell
698 |
699 | ```bash
700 | # Entry in ~/.ssh/config to simplify SSH for macOS/Linux.
701 |
702 | Host pyprod-host
703 | HostName pyprod-host
704 | User root
705 | IdentityFile ~/.ssh/
706 | ```
707 |
708 | ### Code block 11-05 - Linux Shell
709 |
710 | ```bash
711 | # Install pending updates on your brand new server.
712 |
713 | $ apt update
714 | $ apt upgrade # Can pass -y to avoid prompting
715 | $ reboot
716 | ```
717 |
718 | ### Code block 11-06 - Linux Shell
719 |
720 | ```bash
721 | # File (fragment 1): book/ch11-example-setup/setup-host-server.sh
722 |
723 | ############################################################
724 | # Setup the server itself including ohmyzsh
725 | apt-get update
726 | apt-get upgrade -y
727 |
728 | # Install ZSH and ohmyzsh
729 | apt-get install zsh -y
730 | sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
731 |
732 | # Btop is an amazing tool for monitoring server behaviors
733 | apt install btop -y
734 |
735 | # We'll need these to operate the server
736 | apt install ca-certificates -y
737 | apt install curl wget -y
738 | apt install git -y
739 |
740 | # And listing directories as trees is very helpful
741 | apt install tree -y
742 |
743 | # Logging into github over and over is a hassle.
744 | # This will remember it until you reboot or it times out.
745 | git config --global credential.helper cache
746 | git config --global credential.helper 'cache --timeout=720000'
747 |
748 | # TODO: Enter your name and email for git in the placeholders.
749 | git config --global user.email "YOUR_EMAIL"
750 | git config --global user.name "YOUR_NAME"
751 |
752 | # Install uv for local Python-based tool management
753 | curl -LsSf https://astral.sh/uv/install.sh | sh
754 | # install pls via uv, a better ls:
755 | uv tool install pls
756 |
757 | # Typing long commands is a hassle, create aliases to make
758 | # them very simple (glances won't run until Docker is setup).
759 |
760 | # Add the following lines to your ~/.zshrc file at the end:
761 | alias http='docker run -it --rm --net=host clue/httpie'
762 | alias glances='docker run --rm --name glances -v /var/run/docker.sock:/var/run/docker.sock:ro -v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock:ro --pid host --network host -it docker.io/nicolargo/glances'
763 | alias deploy="/cluster-src/book/ch11-example-setup/scripts/deploy.sh"
764 | alias dc="docker compose"
765 | alias lls="/bin/ls -G"
766 | alias ls="pls"
767 |
768 | # Once you edit and save your .zshrc file, reload the config:
769 | source ~/.zshrc
770 | ```
771 |
772 | ### Code block 11-07 - Linux Shell
773 |
774 | ```bash
775 | # File (fragment 2): book/ch11-example-setup/setup-host-server.sh
776 |
777 | ############################################################
778 | # Setup docker on the server.
779 |
780 | # Add Docker's official GPG key:
781 | sudo apt-get update
782 | sudo apt-get install ca-certificates curl -y
783 | sudo install -m 0755 -d /etc/apt/keyrings
784 | sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
785 | sudo chmod a+r /etc/apt/keyrings/docker.asc
786 |
787 | # Add the repository to Apt sources:
788 | echo \
789 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
790 | $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
791 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
792 | sudo apt-get update
793 |
794 | # To install the latest version of Docker
795 | sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
796 | ```
797 |
798 | ### Code block 11-08 - Linux Shell
799 |
800 | ```bash
801 | # Test Docker by running hello-world.
802 | docker run hello-world
803 | ```
804 |
805 | ### Code block 11-09 - Linux Shell
806 |
807 | ```bash
808 | ############################################################
809 | # Create the persistent docker elements (network, disks, etc)
810 | docker network create -d bridge cluster-network --subnet=174.44.0.0/16
811 | ```
812 |
813 | ### Code block 11-10 - Linux Shell
814 |
815 | ```bash
816 | # Example command to create an external Docker volume (disk).
817 |
818 | # Do NOT run this command, it's just for your reference.
819 | # We are not using external volumes in our example.
820 | docker volume create NAME
821 | ```
822 |
823 | ### Code block 11-11 - Linux Shell
824 |
825 | ```bash
826 | # Remember to use your fork of this repo.
827 | git clone https://github.com/mikeckennedy/talk-python-in-production-devops-book /cluster-src/
828 | ```
829 |
830 | ### Code block 11-12 - Linux Shell
831 |
832 | ```bash
833 | cd /cluster-src/book/ch11-example-setup/containers/core-app/video-collector-docker/src
834 | # Remember to use your fork of this repo.
835 | git clone https://github.com/talkpython/htmx-python-course
836 | ```
837 |
838 | ### Code block 11-13 - Docker
839 |
840 | ```dockerfile
841 | # File: ch11-example-setup/containers/base-images/linuxbase/Dockerfile
842 | FROM ubuntu:latest
843 |
844 | # Set your language and time zone so the server
845 | # is in the same time as you or your company.
846 | ENV LANG=en_US.UTF-8
847 | ENV LANGUAGE=en_US:en
848 | ENV LC_ALL=en_US.UTF-8
849 | ENV TZ="America/Los_Angeles"
850 |
851 | # Run many commands without confirmations.
852 | ENV DEBIAN_FRONTEND=noninteractive
853 |
854 | # Apply the latest patches and security fixes.
855 | RUN apt-get update
856 | RUN apt-get upgrade -y
857 |
858 | # Foundational tooling.
859 | RUN apt-get -y install curl
860 | RUN apt-get -y install wget
861 | RUN apt-get install -y openssl
862 | RUN apt-get -y install locales
863 | RUN locale-gen en_US.UTF-8
864 |
865 | # Tools for source code and installing packages.
866 | RUN apt-get -y install git
867 | RUN apt-get install -y gcc
868 | RUN apt-get install -y clang
869 |
870 | # Install Oh My ZSH for a nicer shell experience.
871 | # Used for commands:
872 | # docker exec -it CONTAINER_NAME zsh
873 | # docker run -it CONTAINER_NAME zsh
874 | RUN apt-get install -y zsh
875 | RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.1/zsh-in-docker.sh)" -- \
876 | -t robbyrussell \
877 | ```
878 |
879 | ### Code block 11-14 - Docker
880 |
881 | ```dockerfile
882 | # File: ch11-example-setup/containers/base-images/pythonbase/Dockerfile
883 | FROM linux-example-base:latest
884 |
885 | # uv and python are not accessible from the path by default.
886 | # So add their paths here.
887 | ENV PATH=/venv/bin:$PATH
888 | ENV PATH=/root/.cargo/bin:$PATH
889 | ENV PATH="/root/.local/bin/:$PATH"
890 |
891 | # Our app will be based in this directory
892 | # regardless of its actual file location on our build machine.
893 | RUN mkdir /apps
894 |
895 | # Install Rust/Cargo for local builds of Rust-based Python packages
896 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
897 |
898 | # Install uv
899 | RUN curl -LsSf https://astral.sh/uv/install.sh | sh
900 |
901 | # Set up a virtual env to use for app destined for derived containers.
902 | # This installs Python 3.13 in addition to creating the virtual environment.
903 | # Incredibly, it only takes two seconds. 🚀 uv!
904 | # Update Python 3.13 to the latest Python version as that evolves over time.
905 | RUN uv venv --python 3.13 /venv
906 |
907 | # Activate the venv if we login via docker run/exec:
908 | RUN echo "\nsource /venv/bin/activate\n" >> /root/.zshrc
909 |
910 | # Install some handy utilities for debugging within the container
911 | RUN --mount=type=cache,target=/root/.cache uv pip install httpie pls
912 |
913 | # Verify that Python is installed and works.
914 | # This will fail the build if something has gone wrong.
915 | RUN /venv/bin/python --version
916 | ```
917 |
918 | ### Code block 11-15 - Linux Shell
919 |
920 | ```bash
921 | # Docker Compose command to build the foundational Docker images.
922 |
923 | cd /cluster-src/book/ch11-example-setup/containers/base-images/
924 | docker compose build linux-example-base
925 | docker compose build python-example-base
926 | ```
927 |
928 | ### Code block 11-16 - Docker
929 |
930 | ```dockerfile
931 | # File: book/ch11-example-setup/containers/base-images/compose.yaml
932 | services:
933 |
934 | # Define the Python base image, depends_on ensures
935 | # linux-example-base is built first.
936 | python-example-base:
937 | build: ./pythonbase
938 | container_name: python-example-base
939 | image: python-example-base
940 | depends_on:
941 | - linux-example-base
942 |
943 | linux-example-base:
944 | build: ./linuxbase
945 | container_name: linux-example-base
946 | image: linux-example-base
947 | ```
948 |
949 | ### Code block 11-17 - Docker
950 |
951 | ```dockerfile
952 | # File: book/ch11-example-setup/containers/core-app/video-collector-docker/Dockerfile
953 |
954 | # Use our python foundational image.
955 | FROM python-example-base:latest
956 |
957 | WORKDIR "/app"
958 |
959 | # Recall our multi-step process to speed up builds discussed in chapter 8:
960 |
961 | # 1. Copy the app's requirements file into the containers' /app directory.
962 | COPY ./src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector/requirements.txt /app
963 | RUN uv self update
964 | RUN --mount=type=cache,target=/root/.cache uv pip install -r requirements.txt
965 |
966 | # 2. Copy the app source code into the containers' /app directory.
967 | COPY src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector /app
968 |
969 | # Make sure we have the latest app server (Granián) if the bases are cached.
970 | # This WILL exist from python-example-base but it may be out of date.
971 | RUN --mount=type=cache,target=/root/.cache uv pip install --upgrade "granian[pname]"
972 |
973 | # Set the command to run when the container starts (Granián).
974 | ENTRYPOINT [ \
975 | "/venv/bin/granian",\
976 | "app:app", \
977 | "--host","0.0.0.0", \
978 | "--port","15000", \
979 | "--interface","wsgi", \
980 | "--no-ws", \
981 | "--workers","2", \
982 | "--runtime-mode", "mt", \
983 | "--runtime-threads", "4",\
984 | "--loop","uvloop", \
985 | "--log-level","info", \
986 | "--log", \
987 | "--process-name", "granian [video-collector]" \
988 | ]
989 | ```
990 |
991 | ### Code block 11-18 - Docker Compose
992 |
993 | ```yaml
994 | # Docker Compose file defining Video Collector's infrastructure settings.
995 | # File: book/ch11-example-setup/containers/core-app/compose.yaml
996 |
997 | services:
998 |
999 | # This defines our web app.
1000 | video-collector:
1001 | # Name the image in the docker images so it's not something random.
1002 | image: video-collector
1003 |
1004 | # Name the container so it's clear when it's running.
1005 | container_name: video-collector
1006 |
1007 | # Restart it if it crashes.
1008 | restart: unless-stopped
1009 |
1010 | # Control which ports are open on the container and
1011 | # what "network" they are available on.
1012 | # "127.0.0.1:15000" is for the host and means that
1013 | # the container can only be reached inside the server.
1014 | # This is another layer of security beyond our cloud firewall.
1015 | # The value for 15000 needs to line up with the granian command.
1016 | ports:
1017 | - "127.0.0.1:15000:15000"
1018 |
1019 | # Map a logfile directory (from .env) to container's /log dir.
1020 | volumes:
1021 | - "${APP_LOGS}:/logs"
1022 |
1023 | # Specify how to build this image, here it's via:
1024 | # ./video-collector-docker/Dockerfile
1025 | build: video-collector-docker
1026 |
1027 | # Limit how much of our server is given over to this container.
1028 | # Doesn't matter in this case, but it's an example for larger clusters.
1029 | deploy:
1030 | resources:
1031 | limits:
1032 | memory: 1G
1033 |
1034 | # Specify which external Docker networks to join.
1035 | # Setting a unique and fixed IP address allows NGINX
1036 | # to find it more reliably (a shortcoming of NGINX).
1037 | networks:
1038 | cluster-network:
1039 | ipv4_address: 174.44.0.100
1040 |
1041 | # This network was created during server setup with the
1042 | # `docker network create` command.
1043 | networks:
1044 | cluster-network:
1045 | name: cluster-network
1046 | external: true
1047 | ```
1048 |
1049 | ### Code block 11-19 - Linux Shell
1050 |
1051 | ```bash
1052 | cd /cluster-src/book/ch11-example-setup/containers/core-app/
1053 | docker compose build
1054 | ```
1055 |
1056 | ### Code block 11-20 - Linux Shell
1057 |
1058 | ```bash
1059 | # Launch the Flask app with Docker Compose
1060 | cd /cluster-src/book/ch11-example-setup/containers/core-app/
1061 | docker compose up
1062 | ```
1063 |
1064 | ### Code block 11-21 - Linux Shell
1065 |
1066 | ```bash
1067 | # Launch the Flask app in Docker Compose in the background.
1068 | docker compose up -d
1069 | ```
1070 |
1071 | ### Code block 11-22 - Linux Shell
1072 |
1073 | ```bash
1074 | # Control the compose apps via:
1075 | docker compose down # shut down and clean up.
1076 | docker compose restart # restart (but not rebuild) all the containers.
1077 | docker compose logs -f -n 100 # Tail the combined logs (text output) of all containers.
1078 | ```
1079 |
1080 | ### Code block 11-23 - Linux Shell
1081 |
1082 | ```bash
1083 | alias http='docker run -it --rm --net=host clue/httpie'
1084 | ```
1085 |
1086 | ### Code block 11-24 - Linux Shell
1087 |
1088 | ```bash
1089 | http -h localhost:15000
1090 | ```
1091 |
1092 | ### Code block 11-25 - Linux Shell
1093 |
1094 | ```bash
1095 | HTTP/1.1 200 OK
1096 | content-length: 4397
1097 | content-type: text/html; charset=utf-8
1098 | server: granian
1099 | ```
1100 |
1101 | ### Code block 11-26 - Linux Shell
1102 |
1103 | ```bash
1104 | book/ch11-example-setup/scripts/create-docker-compose-service.sh
1105 | ```
1106 |
1107 | ### Code block 11-27 - Linux Shell
1108 |
1109 | ```bash
1110 | # Create a systemd daemon for Video Collector.
1111 | cd /cluster-src/book/ch11-example-setup/containers/core-app/
1112 | bash /cluster-src/book/ch11-example-setup/scripts/create-docker-compose-service.sh
1113 | ```
1114 |
1115 | ### Code block 11-28 - Linux Shell
1116 |
1117 | ```bash
1118 | Creating systemd service... /etc/systemd/system/core-app.service
1119 | Enabling & starting core-app
1120 | ```
1121 |
1122 | ### Code block 11-29 - Linux Shell
1123 |
1124 | ```bash
1125 | # Check on the new Docker Compose based service.
1126 | service core-app status
1127 | ```
1128 |
1129 | ### Code block 11-30 - Linux Shell
1130 |
1131 | ```bash
1132 | # Reboot the server to verify the service is auto-starting.
1133 | reboot
1134 |
1135 | # Wait 10 seconds for the server to start.
1136 | # Reconnect
1137 | ssh root@pyprod-host
1138 |
1139 | # Use httpie to call the app, should get 200 OK.
1140 | http -h localhost:15000
1141 |
1142 | # you can also test that the container is running:
1143 | docker ps
1144 | ```
1145 |
1146 | ### Code block 11-31 - Docker
1147 |
1148 | ```dockerfile
1149 | # File: book/ch11-example-setup/containers/web-servers/compose.yaml
1150 |
1151 | services:
1152 |
1153 | # Our custom configuration for NGINX.
1154 | nginx:
1155 | # We run the latest base image.
1156 | image: nginx:latest
1157 |
1158 | # Specify a container name so it's easy to see in management tools.
1159 | container_name: nginx
1160 |
1161 | # If the container crashes for some reason, restart it.
1162 | restart: unless-stopped
1163 |
1164 | # We need to map both port 80 and 443
1165 | # to the top level network on the host.
1166 | # Notice there is no 127.0.0.1 like with our Flask app.
1167 | ports:
1168 | - "80:80"
1169 | - "443:443"
1170 |
1171 | # There's a lot of configuration and logging
1172 | # that needs to happen for NGINX. This section
1173 | # a bunch of directories that will go into greater
1174 | # detail about later.
1175 | volumes:
1176 | - "./local-nginx.conf:/etc/nginx/nginx.conf"
1177 | - "${NGINX_SITES_ENABLED}:/etc/nginx/sites-enabled"
1178 | - "${NGINX_LOGS}:/var/log/nginx/"
1179 | - "${NGINX_STATIC}:/static/"
1180 | - "${NGINX_LETSENCRYPT_WWW}:/var/www/letsencrypt"
1181 | - "${CERTBOT_WWW}:/var/www/certbot/"
1182 | - "${NGINX_LETS_ENCRYPT_ETC}:/etc/letsencrypt/"
1183 |
1184 | # NGINX has a nice health check that communicates
1185 | # to Docker whether it's running correctly.
1186 | healthcheck:
1187 | test: service nginx status || exit 1
1188 | interval: 30s
1189 | retries: 5
1190 | start_period: 10s
1191 | timeout: 1s
1192 |
1193 | # we can limit the amount of server allowed by nginx as well
1194 | deploy:
1195 | resources:
1196 | limits:
1197 | memory: 1G
1198 |
1199 | # NGINX and the other containers need to share
1200 | # the same network so that HTTP requests can
1201 | # be passed through to the Python app.
1202 | networks:
1203 | cluster-network:
1204 |
1205 | # We discussed the use of certbot and let's encrypt in chapter 9.
1206 | # However, we're not going into further detail here
1207 | # because that requires access to proper DNS and
1208 | # other settings that are a bit beyond the goal of this chapter.
1209 | # This block is only here for completeness.
1210 | certbot:
1211 | image: certbot/certbot:latest
1212 | container_name: certbot
1213 | volumes:
1214 | - "${CERTBOT_WWW}:/var/www/certbot/"
1215 | - "${NGINX_LETS_ENCRYPT_ETC}:/etc/letsencrypt/:rw"
1216 | networks:
1217 | cluster-network:
1218 |
1219 | # Remember that we discussed creating this external network above.
1220 | networks:
1221 | cluster-network:
1222 | name: cluster-network
1223 | external: true
1224 | ```
1225 |
1226 | ### Code block 11-32 - Linux Shell
1227 |
1228 | ```bash
1229 | ############################################################
1230 | # Make the static folders for data exchange between the
1231 | # containers, git updates, and data exports
1232 | mkdir -p /cluster-data/
1233 | mkdir -p /cluster-data/nginx/static
1234 | mkdir -p /cluster-data/nginx/logs
1235 | mkdir -p /cluster-data/logs/video-collector
1236 |
1237 | mkdir -p /cluster-data/nginx/letsencrypt-etc
1238 | mkdir -p /cluster-data/nginx/letsencrypt-www
1239 | mkdir -p /cluster-data/nginx/certbot/www
1240 | ```
1241 |
1242 | ### Code block 11-33 - Linux Shell
1243 |
1244 | ```bash
1245 | # Copy dot-env-template.sh to .env and edit .env for NGINX
1246 | cd book/ch11-example-setup/containers/web-servers/
1247 | cp dot-env-template.sh .env
1248 | nano .env
1249 | ```
1250 |
1251 | ### Code block 11-34 - Linux Shell
1252 |
1253 | ```bash
1254 | # Launch NGINX with Docker Compose.
1255 | cd book/ch11-example-setup/containers/web-servers/
1256 | docker compose up
1257 | ```
1258 |
1259 | ### Code block 11-35 - Linux Shell
1260 |
1261 | ```bash
1262 | # Test our empty NGINX container is handling requests.
1263 | http -h localhost
1264 |
1265 | # Output:
1266 | HTTP/1.1 404 Not Found
1267 | Connection: keep-alive
1268 | Content-Length: 153
1269 | Content-Type: text/html
1270 | Server: nginx/1.27.3
1271 | ```
1272 |
1273 | ### Code block 11-36 - Linux Shell
1274 |
1275 | ```bash
1276 | # Create a systemd daemon for NGINX.
1277 | cd /cluster-src/book/ch11-example-setup/containers/web-servers/
1278 | bash /cluster-src/book/ch11-example-setup/scripts/create-docker-compose-service.sh
1279 | ```
1280 |
1281 | ### Code block 11-37 - Linux Shell
1282 |
1283 | ```bash
1284 | # Output from systemd daemon NGINX script.
1285 | Creating systemd service... /etc/systemd/system/web-servers.service
1286 | Enabling & starting core-app
1287 | ```
1288 |
1289 | ### Code block 11-38 - NGINX
1290 |
1291 | ```nginx
1292 | # File: book/ch11-example-setup/containers/web-servers/nginx-base-configs/video-collector.nginx
1293 |
1294 | server {
1295 | # The server domain is your public DNS name.
1296 | # We're just going to change our local machine DNS and use this one.
1297 | server_name video-collector-test.talkpython.com;
1298 | listen 80;
1299 | charset utf-8;
1300 | client_max_body_size 1M;
1301 |
1302 | # Don't return the NGINX Version in case there's a vulnerability
1303 | # that can be searched for.
1304 | server_tokens off;
1305 |
1306 | # NGINX will serve the static files directly,
1307 | # so we set them up separately from the application.
1308 | location /static {
1309 | gzip on;
1310 |
1311 | gzip_comp_level 6;
1312 | gzip_min_length 1100;
1313 | gzip_buffers 16 8k;
1314 | gzip_proxied any;
1315 | gzip_types
1316 | text/plain
1317 | text/xml
1318 | text/css
1319 | application/javascript
1320 | application/json
1321 | application/xml
1322 | application/rss+xml;
1323 |
1324 | # Specify, relative to the internals of the Docker container,
1325 | # where the static files live. For example
1326 | # http://video-collector-test.talkpython.com/static/img/logo.jpg
1327 | # will find the file in /static/video-collector/img/logo.jpg
1328 | # on THIS internal Docker file system.
1329 | alias /static/video-collector;
1330 | expires 365d;
1331 | }
1332 |
1333 | # Here for let's encrypt support, not used in example.
1334 | location /.well-known/acme-challenge/ {
1335 | root /var/www/certbot;
1336 | }
1337 |
1338 | # This handles the rest of the requests to this web app.
1339 | location / {
1340 | try_files $uri @yourapplication;
1341 | }
1342 | # Here is where we tell NGINX to pass commands over to
1343 | # our production app server running on Granian.
1344 | location @yourapplication {
1345 | gzip on;
1346 | gzip_comp_level 6;
1347 | gzip_min_length 1100;
1348 | gzip_buffers 8 256k;
1349 | gzip_proxied any;
1350 | gzip_types
1351 | text/plain
1352 | text/xml
1353 | text/css
1354 | application/javascript
1355 | application/json
1356 | application/xml
1357 | application/rss+xml;
1358 |
1359 | # Recall that we explicitly specified this IP
1360 | # address in port in the Video Collector
1361 | # docker-compose configuration. The IP address
1362 | # is for the internal Docker network that we created.
1363 | proxy_pass http://174.44.0.100:15000;
1364 |
1365 | include uwsgi_params;
1366 | proxy_set_header Host $host;
1367 | proxy_set_header X-Real-IP $remote_addr;
1368 | proxy_set_header X-Forwarded-Protocol $scheme;
1369 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
1370 | proxy_set_header X-Forwarded-Host $host;
1371 | }
1372 | }
1373 | ```
1374 |
1375 | ### Code block 11-39 - Linux Shell
1376 |
1377 | ```bash
1378 | # Copy static files over to a static location visible to NGINX.
1379 | cp -r /cluster-src/book/ch11-example-setup/containers/core-app/video-collector-docker/src/htmx-python-course/code/ch7_infinite_scroll/ch7_final_video_collector/static/* /cluster-data/nginx/static/video-collector
1380 | ```
1381 |
1382 | ### Code block 11-40 - Linux Shell
1383 |
1384 | ```bash
1385 | # Verify the mapped static folder contains the correct file structure.
1386 | tree /cluster-data/nginx/static/video-collector -d
1387 |
1388 | # Output
1389 | /cluster-data/nginx/static/video-collector
1390 | ├── css
1391 | ├── fontawesome-free
1392 | │ ├── css
1393 | │ └── webfonts
1394 | ├── img
1395 | │ └── categories
1396 | └── js
1397 | ```
1398 |
1399 | ### Code block 11-41 - Linux Shell
1400 |
1401 | ```bash
1402 | # Reload and update NGINX's configuration files.
1403 | cd /cluster-src/book/ch11-example-setup/containers/web-servers/
1404 | docker compose exec -t nginx nginx -s reload
1405 | ```
1406 |
1407 | ### Code block 11-42 - Hosts file
1408 |
1409 | ```bash
1410 | # On YOUR CLIENT machine's hosts, enter:
1411 |
1412 | # Existing
1413 | 20.21.22.23 pyprod-host
1414 |
1415 | # Add
1416 | 20.21.22.23 video-collector-test.talkpython.com
1417 | ```
1418 |
1419 |
1420 | ## Chapter 12: Static Sites and Hugo
1421 |
1422 | ### Code block 12-01 - NGINX
1423 |
1424 | ```nginx
1425 | # NGINX configuration file for https://mkennedy.codes (static site)
1426 | server {
1427 | listen 80;
1428 | # Redirect a bunch of other domains I've used back to this site.
1429 | server_name www.mkennedy.codes mkennedy.tech www.mkennedy.tech michaelckennedy.net www.michaelckennedy.net michaelckennedy.com www.michaelckennedy.com;
1430 |
1431 | # Here is the redirect statement
1432 | return 301 https://mkennedy.codes$request_uri;
1433 | server_tokens off;
1434 | }
1435 |
1436 | server {
1437 | listen 80;
1438 | # Listen for the domain of the static site.
1439 | server_name mkennedy.codes;
1440 | charset utf-8;
1441 | server_tokens off;
1442 |
1443 | # THESE TWO ARE THE CRITICAL BITS
1444 |
1445 | # The root of this site is just a location where we
1446 | # git cloned the files generated by Hugo to ./prod
1447 | root /apps/static/mkennedy.codes/mkennedy-codes/prod;
1448 |
1449 | # Tell NGINX that if it sees a path that is a folder
1450 | # not a file, use index.html as the content.
1451 | # e.g with the URL:
1452 | # https://mkennedy.codes/posts/blue-skies-ahead-follow-me-there/
1453 | # render the file:
1454 | # /apps/static/mkennedy.codes/mkennedy-codes/prod/posts/blue-skies-ahead-follow-me-there/index.html
1455 | index index.html;
1456 |
1457 | # Use alias for the ACME/Let's Encrypt challenge directory
1458 | location /.well-known/acme-challenge/ {
1459 | alias /var/www/certbot/.well-known/acme-challenge/;
1460 | try_files $uri =404;
1461 | }
1462 |
1463 | # Add 2 minutes of caching, just locally per browser
1464 | # on each top-level page.
1465 | location ~* .(html)$ {
1466 | expires 2m;
1467 | add_header Cache-Control "private";
1468 | add_header Cache-Control "max-age=120"; # set cache timeout to 2 min for pages
1469 | }
1470 |
1471 | # Add 1 hours' worth of caching on images, css, etc.
1472 | location ~* .(png|webp|ico|gif|jpg|jpeg|css|js)$ {
1473 | expires 1h;
1474 | add_header Cache-Control "public";
1475 | add_header Cache-Control "max-age=3600"; # set cache timeout to 1 hour
1476 | }
1477 |
1478 | # gzip our text content for much faster delivery.
1479 | gzip on;
1480 |
1481 | gzip_comp_level 6;
1482 | gzip_min_length 1100;
1483 | gzip_buffers 16 8k;
1484 | gzip_proxied any;
1485 | gzip_types
1486 | text/plain
1487 | text/xml
1488 | text/css
1489 | application/javascript
1490 | application/json
1491 | application/xml
1492 | application/rss+xml;
1493 | }
1494 | ```
1495 |
1496 | ### Code block 12-02 - Linux Shell
1497 |
1498 | ```bash
1499 | hugopublish="cd [LOCAL_SITE_FOLDER] && pwd && git pull && hugo build --destination public-prod --cleanDestinationDir && git add **/. && git commit -m \"Added recent build contents\" && ssh [HOST_NAME] \"echo \"Updating mkennedy.codes\" && cd [SERVER_SITE_FOLDER]/prod && git pull"
1500 | ```
1501 |
1502 | ### Code block 12-03 - NGINX
1503 |
1504 | ```nginx
1505 | server {
1506 | listen 80;
1507 | server_name talkpython.fm;
1508 | server_tokens off;
1509 |
1510 | # ...
1511 |
1512 | # Map a subroute to /blog, see next two blocks
1513 | # talkpython.fm/blog location (no caching for HTML content)
1514 | location /blog {
1515 | alias /apps/static/talkpython/talk-python-blog/prod/;
1516 | index index.html;
1517 |
1518 | # Disable caching for the main content
1519 | add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
1520 | expires off;
1521 | }
1522 |
1523 | # Separate location block for static assets (CSS, JS, images) to enable caching
1524 | location ~* /blog/(.+\.(css|js|jpg|jpeg|png|webp|gif|svg|ico|woff|woff2|ttf|otf))$ {
1525 | # Similar to the prior example...
1526 | }
1527 |
1528 | # Critical that this portion comes after /blog
1529 | # since it's effectively catching all URLs.
1530 | location / {
1531 | try_files $uri @talk_python_app;
1532 | }
1533 | location @talk_python_app { ... }
1534 | }
1535 | ```
1536 |
1537 | ### Code block 12-04 - Python
1538 |
1539 | ```python
1540 | # Python code to pull sitemap entries from nested static site
1541 | # This is used to add them back to the root website's sitemap.
1542 | BlogMapEntry = namedtuple('BlogMapEntry', ['loc', 'modified'])
1543 |
1544 | def get_items_from_blog_sitemap() -> list[BlogMapEntry]:
1545 | resp = requests.get('https://talkpython.fm/blog/sitemap.xml')
1546 | resp.raise_for_status()
1547 |
1548 | ns = {'ns': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
1549 | root = ElementTree.fromstring(resp.text)
1550 |
1551 | excludes = ['/categories/', '/tags/']
1552 |
1553 | # Extract loc and lastmod elements
1554 | url_info = []
1555 | for url in root.findall('ns:url', ns):
1556 | loc = url.find('ns:loc', ns).text
1557 | lastmod = url.find('ns:lastmod', ns)
1558 | lastmod_text = lastmod.text if lastmod is not None else None
1559 | entry = BlogMapEntry(loc, lastmod_text)
1560 |
1561 | skip = False
1562 | for exclude in excludes:
1563 | if exclude in entry.loc:
1564 | skip = True
1565 | break
1566 |
1567 | if skip:
1568 | continue
1569 |
1570 | url_info.append(entry)
1571 |
1572 | return url_info
1573 | ```
1574 |
1575 |
1576 | ## Chapter 13: Picking a Python Web Framework
1577 |
1578 | ### Code block 13-01 - Python
1579 |
1580 | ```python
1581 | # Flask blueprints that define Talk Python's global structure.
1582 |
1583 | import quart
1584 | # ...
1585 |
1586 | def register_blueprints(app: quart.Quart):
1587 | # Needs to appear first.
1588 | app.register_blueprint(episodes_blueprint)
1589 |
1590 | app.register_blueprint(home_blueprint)
1591 | app.register_blueprint(friends_blueprint)
1592 | app.register_blueprint(advertiser_blueprint)
1593 | app.register_blueprint(search_blueprint)
1594 | app.register_blueprint(stream_blueprint)
1595 | app.register_blueprint(sitemap_blueprint)
1596 | app.register_blueprint(policies_blueprint)
1597 | app.register_blueprint(account_blueprint)
1598 | app.register_blueprint(admin_blueprint)
1599 | app.register_blueprint(error_blueprint)
1600 | app.register_blueprint(hackers_blueprint)
1601 |
1602 | # URL shortener needs to be last in line.
1603 | app.register_blueprint(redirector_blueprint)
1604 | ```
1605 |
1606 | ### Code block 13-02 - Python
1607 |
1608 | ```python
1609 | # Example of an asynchronous Flask view using chameleon_flask
1610 |
1611 | @app.get('/catalog/item/{item_id}')
1612 | @chameleon_flask.template('catalog/item.pt')
1613 | async def item(item_id: int):
1614 | item = service.get_item_by_id(item_id)
1615 | if not item:
1616 | return chameleon_flask.not_found()
1617 |
1618 | return item.dict()
1619 | ```
1620 |
1621 | ### Code block 13-03 - Python
1622 |
1623 | ```python
1624 | # Async data access leads to a secondary init function
1625 |
1626 | @episodes_blueprint.get('/')
1627 | async def show_by_number(show_id: int):
1628 | vm = ShowEpisodeViewMode(show_id)
1629 | await vm.load_async() # Now needed for await
1630 | if vm.episode is None:
1631 | quart.abort(404)
1632 |
1633 | return webutils.redirect_to(vm.episode.details_action_url, permanent=True)
1634 | ```
1635 |
1636 |
1637 |
--------------------------------------------------------------------------------