├── 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 | [![](./images/devops-book-web-hero.webp)](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 | [![Workload over time at our server](figures/03-01-workload-talkpython-apps.png)](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 | [![Visualization of Linux running Docker running our infrastructure.](figures/05-01-architecture-tiers.png)](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 | [![View of processes on Docker host machine.](figures/05-02-granian-lots-of-granian.png)](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 | [![Abstract visualization of our Docker infrastructure.](figures/06-01-server-tree.png)](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 | [![Event graph in Umami.](figures/06-02-events-chart.png)](figures/06-02-events-chart.png) 52 | Figure 06-02: [Event graph in Umami.](figures/06-02-events-chart.png) 53 | 54 | 55 | [![Uptime Kuma home page.](figures/06-03-uptime-kuma.png)](figures/06-03-uptime-kuma.png) 56 | Figure 06-03: [Uptime Kuma home page.](figures/06-03-uptime-kuma.png) 57 | 58 | 59 | [![Uptime Kuma status page for Talk Python.](figures/06-04-status-talkpython.png)](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 | [![btop dashboard updating real-time at Talk Python.](figures/07-01-btop-dashboard.jpg)](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 | [![btop's graphical options screen, over SSH!](figures/07-02-btop-options.jpg)](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 | [![Glances dashboard updating real-time at Talk Python.](figures/07-03-glances.jpg)](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 | [![Docker Cluster Monitor of the containers at Talk Python.](figures/07-04-docker-cluster-monitor.jpg)](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 | [![Architectural view of NGINX and production app server on Docker + Linux](figures/05-01-architecture-tiers.png)](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 | [![119 POPs (Points of Presence) for our CDN](figures/10-01-cdn-pops.jpg)](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 | [![Talk Python's CDN traffic over two weeks](figures/10-02-bunny-stats.png)](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 | [![Hetzner dashboard showing two projects](figures/11-01-hetzner-projects.png)](figures/11-01-hetzner-projects.png) 112 | Figure 11-01: [Hetzner dashboard showing two projects](figures/11-01-hetzner-projects.png) 113 | 114 | 115 | [![Low capability default Bash shell](figures/11-02-bash-basics.png)](figures/11-02-bash-basics.png) 116 | Figure 11-02: [Low capability default Bash shell](figures/11-02-bash-basics.png) 117 | 118 | 119 | [![High capability OhMyZSH shell](figures/11-03-bash-basics.pngohmyzsh-completion.png)](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 | [![Output from developer-friendly pls (ls alternative)](figures/11-04-pls-listing.png)](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 | [![Conceptualization of layered architecture with multiple Docker images](figures/11-05-docker-layers.png)](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 | [![Successful build using Docker Compose](figures/11-06-docker-compose-output-base-images.png)](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 | [![Screenshot of Video Collector Flask app (our demo app)](figures/11-07-video-collector.png)](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 | [![Successful Docker Compose build of our Flask app](figures/11-08-build-video-collector.png)](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 | [![Launch the Flask app with Docker Compose](figures/11-09-running-video-collector.png)](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 | [![Checking on the new Docker Compose based service](figures/11-10-service-status.png)](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 | [![Launch NGINX with Docker Compose](figures/11-11-nginx-up.png)](figures/11-11-nginx-up.png) 152 | Figure 11-11: [Launch NGINX with Docker Compose](figures/11-11-nginx-up.png) 153 | 154 | 155 | [![Our Video Collector Flask app, running in Docker](figures/11-07-video-collector.png)](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 | [![Deploying our app end-to-end with a script](figures/11-12-deploy-output.png)](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 | [![Hugo static site generator](figures/12-01-hugo.png)](figures/12-01-hugo.png) 169 | Figure 12-01: [Hugo static site generator](figures/12-01-hugo.png) 170 | 171 | 172 | [![Lextree from Bearkman Solutions, built entirely with Hugo](figures/12-02-berkmansolutions-1.png)](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 | [![Data driven pages, but from markdown and Hugo](figures/12-03-berkmansolutions-2.png)](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 | [![Embedded CSV spreadsheet tools with Hugo](figures/12-04-berkmansolutions-3.png)](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 | [![Michael's personal website](figures/12-05-mkennedy-codes.png)](figures/12-05-mkennedy-codes.png) 185 | Figure 12-05: [Michael's personal website](figures/12-05-mkennedy-codes.png) 186 | 187 | 188 | [![Talk Python's official blog](figures/12-06-talk-python-blog.png)](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 | [![Popularity of Python web frameworks](figures/13-01-frameworks.png)](figures/13-01-frameworks.png) 198 | Figure 13-01: [Popularity of Python web frameworks](figures/13-01-frameworks.png) 199 | 200 | 201 | [![PR migrating Talk Python from Pyramid to Quart (sync only)](figures/13-02-webapp-to-quart.png)](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 | [![PR migrating Talk Python from Pyramid to Quart (async step)](figures/13-03-quart-to-async.png)](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 | [![Screenshot of Podbean hosting a podcast](figures/14-01-other-podcast.jpg)](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 | [![Talk Python's episode page circa 2024](figures/14-02-us-podcast.jpg)](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 | [![Talk Python courses video player](figures/14-03-courses.jpg)](figures/14-03-courses.jpg) 223 | Figure 14-03: [Talk Python courses video player](figures/14-03-courses.jpg) 224 | 225 | 226 | [![YouTube live stream of Talk Python episode](figures/14-04-live-youtube.jpg)](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 | [![talkpython.fm in live streaming mode](figures/14-05-live-site.jpg)](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 | [![Social media post from episode featuring YouTube thumbnail](figures/14-06-twitter-share.jpg)](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 | [![Cloud dashboard at Hetzner](figures/15-01-hetzner-dashboard.png)](figures/15-01-hetzner-dashboard.png) 244 | Figure 15-01: [Cloud dashboard at Hetzner](figures/15-01-hetzner-dashboard.png) 245 | 246 | 247 | [![Our server specifications](figures/15-02-hetzner-server.png)](figures/15-02-hetzner-server.png) 248 | Figure 15-02: [Our server specifications](figures/15-02-hetzner-server.png) 249 | 250 | 251 | [![Bandwidth and CPU compared between Hetzner and DigitalOcean](figures/15-03-bandwidth-and-cpu-compared.png)](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 | [![Architectural diagram for a theoretical Cloud-native app](figures/16-01-cloud-native-diagram-final.png)](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 | [![Architectural diagram for our Flask app](figures/16-01-stack-native-to-scale-final.png)](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 | [![Architectural diagram for our Flask app (close-up)](figures/16-01-stack-native-final.png)](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 | --------------------------------------------------------------------------------