Exercise Python Application

Lesson: https://medium.com/devops-technical-notes-and-manuals/devops-example-project-for-your-resume-db45a77d6607

Github: https://github.com/blazingraptor/exercise-python-application.git

Docker Hub: https://hub.docker.com/repository/docker/blazingraptor/exercise-python-application/general

Getting Started

1. Clone it

blazingraptor@galactica:~/git$ git clone https://github.com/blazingraptor/exercise-python-application.git
Cloning into 'exercise-python-application'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.

2a. Create virtual enviornment

blazingraptor@galactica:~/git/exercise-python-application$ python3 -m venv ~/git/exercise-python-application/venv
The virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

    apt install python3.10-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: /home/blazingraptor/git/exercise-python-application/venv/bin/python3

2b. Install it

blazingraptor@galactica:~/git/exercise-python-application$ su -
(truncated output)
root@galactica:~# apt install python3.10-venv
(truncated output)

2c. Re-try Create virtual enviornment

blazingraptor@galactica:~/git/exercise-python-application$ python3 -m venv ~/git/exercise-python-application/venv

2d. Install Flask

blazingraptor@galactica:~/git/exercise-python-application$ pip3 install flask
Defaulting to user installation because normal site-packages is not writeable
Collecting flask
  Downloading flask-3.1.0-py3-none-any.whl (102 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 103.0/103.0 KB 2.3 MB/s eta 0:00:00
Collecting Jinja2>=3.1.2
  Downloading jinja2-3.1.5-py3-none-any.whl (134 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 134.6/134.6 KB 9.1 MB/s eta 0:00:00
Collecting blinker>=1.9
  Downloading blinker-1.9.0-py3-none-any.whl (8.5 kB)
Collecting itsdangerous>=2.2
  Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Collecting click>=8.1.3
  Downloading click-8.1.8-py3-none-any.whl (98 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 98.2/98.2 KB 14.3 MB/s eta 0:00:00
Collecting Werkzeug>=3.1
  Downloading werkzeug-3.1.3-py3-none-any.whl (224 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 224.5/224.5 KB 13.8 MB/s eta 0:00:00
Collecting MarkupSafe>=2.0
  Downloading MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20 kB)
Installing collected packages: MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, flask
  WARNING: The script flask is installed in '/home/blazingraptor/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed Jinja2-3.1.5 MarkupSafe-3.0.2 Werkzeug-3.1.3 blinker-1.9.0 click-8.1.8 flask-3.1.0 itsdangerous-2.2.0

2e. Run your application

blazingraptor@galactica:~/git/exercise-python-application$ python3 app.py
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5001
 * Running on http://172.28.218.96:5001
Press CTRL+C to quit

2f. Test it locally

http://localhost:5001/

It's a Flask application running on POD!

3a. Create Dockerfile

blazingraptor@galactica:~/git/exercise-python-application$ docker init

Welcome to the Docker Init CLI!

This utility will walk you through creating the following files with sensible defaults for your project:
  - .dockerignore
  - Dockerfile
  - compose.yaml
  - README.Docker.md

Let's get started!

? What application platform does your project use? Python
? What version of Python do you want to use? 3.10.12
? What is the command you use to run your app? app.run(host='0.0.0.0', port=5001, debug=false)

✔ Created → .dockerignore
✔ Created → Dockerfile
✔ Created → compose.yaml
✔ Created → README.Docker.md

→ Your Docker files are ready!
  Review your Docker files and tailor them to your application.
  Consult README.Docker.md for information about using the generated files.

! Warning → No requirements.txt file found. Create one with the dependencies for your application before running it.

What's next?
  Start your application by running → docker compose up --build
  Your application will be available at http://localhost:5001

3b. Docker compose

blazingraptor@galactica:~/git/exercise-python-application$ docker compose up --build
[+] Building 3.9s (11/12)                                                                            docker:default
 => [server internal] load build definition from Dockerfile                                                    0.0s
 => => transferring dockerfile: 1.69kB                                                                         0.0s
 => [server] resolve image config for docker-image://docker.io/docker/dockerfile:1                             1.2s
 => [server] docker-image://docker.io/docker/dockerfile:1@sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e  1.0s
 => => resolve docker.io/docker/dockerfile:1@sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d80  0.0s
 => => sha256:93bfd3b68c109427185cd78b4779fc82b484b0b7618e36d0f104d4d801e66d25 8.40kB / 8.40kB                 0.0s
 => => sha256:6427b0634e7650a14afc322b71a37b4654b4471539d1f9a19cb16525a2fb2e56 850B / 850B                     0.0s
 => => sha256:6e15488ac914a453a6e13f419cde418c67927d93d6b0a0f23b5c70c8ecda3fc6 1.26kB / 1.26kB                 0.0s
 => => sha256:8a2af9a64344571e7f712dde5e52bb25729d3ea0f3208ec86dd5af836b4ef1b9 12.78MB / 12.78MB               0.8s
 => => extracting sha256:8a2af9a64344571e7f712dde5e52bb25729d3ea0f3208ec86dd5af836b4ef1b9                      0.2s
 => [server internal] load build definition from Dockerfile                                                    0.0s
 => WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 10)                                0.0s
 => [server internal] load metadata for docker.io/library/python:3.10.12-slim                                  1.0s
 => [server internal] load .dockerignore                                                                       0.0s
 => => transferring context: 671B                                                                              0.0s
 => CANCELED [server base 1/5] FROM docker.io/library/python:3.10.12-slim@sha256:4d440b214e447deddc0a94de23a3  0.2s
 => => resolve docker.io/library/python:3.10.12-slim@sha256:4d440b214e447deddc0a94de23a3d97d28dfafdf125a8b4bb  0.0s
 => => sha256:4d440b214e447deddc0a94de23a3d97d28dfafdf125a8b4bb8073381510c9ee2 1.65kB / 1.65kB                 0.0s
 => => sha256:13cc673c11ee90d6ba92d95f35f4d8e59148937f1e3b4044788e93268bfe9d2e 1.37kB / 1.37kB                 0.0s
 => => sha256:f31204aad67273a64cc5b0e64e2a613ded5d817d9094b02d37db6fd522933b16 6.92kB / 6.92kB                 0.0s
 => => sha256:52d2b7f179e32b4cbd579ee3c4958027988f9a8274850ab0c7c24661e3adaac5 0B / 29.12MB                    0.2s
 => => sha256:2b8a9a2240c1224b34f6aafbc3310f9a3fe65bd6893050906d02e89fc8326aa9 0B / 3.50MB                     0.2s
 => => sha256:618a49bbc6c68a58b082ceb072a9464370d0203a55c70e9cfc16caf6c3c8f383 0B / 17.43MB                    0.2s
 => [server internal] load build context                                                                       0.2s
 => => transferring context: 9.31MB                                                                            0.2s
 => CACHED [server base 2/5] WORKDIR /app                                                                      0.0s
 => CACHED [server base 3/5] RUN adduser     --disabled-password     --gecos ""     --home "/nonexistent"      0.0s
 => ERROR [server base 4/5] RUN --mount=type=cache,target=/root/.cache/pip     --mount=type=bind,source=requi  0.0s
------
 > [server base 4/5] RUN --mount=type=cache,target=/root/.cache/pip     --mount=type=bind,source=requirements.txt,target=requirements.txt     python -m pip install -r requirements.txt:
------
failed to solve: failed to compute cache key: failed to calculate checksum of ref 63f6572d-a101-41fc-915a-3e508325c64c::rztvyqa9p4l5me5aetep5reu9: "/requirements.txt": not found

3c. Fix it

https://www.freecodecamp.org/news/python-requirementstxt-explained/

blazingraptor@galactica:~/git/exercise-python-application$ pip3 freeze > requirements.txt
blazingraptor@galactica:~/git/exercise-python-application$ less requirements.txt
blazingraptor@galactica:~/git/exercise-python-application$ cat requirements.txt
blinker==1.9.0
click==8.1.8
command-not-found==0.3
configobj==5.0.6
cryptography==3.4.8
dbus-python==1.2.18
distro==1.7.0
distro-info==1.1+ubuntu0.2
Flask==3.1.0
httplib2==0.20.2
importlib-metadata==4.6.4
itsdangerous==2.2.0
jeepney==0.7.1
Jinja2==3.1.5
keyring==23.5.0
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
MarkupSafe==3.0.2
more-itertools==8.10.0
netifaces==0.11.0
oauthlib==3.2.0
psutil==5.9.0
pycairo==1.20.1
PyGObject==3.42.1
PyJWT==2.3.0
pyparsing==2.4.7
python-apt==2.4.0+ubuntu3
PyYAML==5.4.1
SecretStorage==3.3.1
six==1.16.0
ssh-import-id==5.11
systemd-python==234
terminator==2.1.1
ubuntu-pro-client==8001
ufw==0.36.1
unattended-upgrades==0.1
wadllib==1.3.6
Werkzeug==3.1.3
zipp==1.0.0

still doesn't work


3d. Fix it again

blazingraptor@galactica:~/git/exercise-python-application$ cp -a requirements.txt requirements.old
blazingraptor@galactica:~/git/exercise-python-application$ vim requirements.txt
blazingraptor@galactica:~/git/exercise-python-application$ cat requirements.txt
Flask==3.1.0
blazingraptor@galactica:~/git/exercise-python-application$ tail -1 Dockerfile
CMD app.run(host='0.0.0.0', port=5001, debug=false)
blazingraptor@galactica:~/git/exercise-python-application$ cp -a Dockerfile Dockerfile.old
blazingraptor@galactica:~/git/exercise-python-application$ vim Dockerfile
blazingraptor@galactica:~/git/exercise-python-application$ tail -1 Dockerfile
CMD python3 app.py

3e. Run it again

blazingraptor@galactica:~/git/exercise-python-application$ docker compose up --build
[+] Building 1.4s (14/14) FINISHED                                                                   docker:default
 => [server internal] load build definition from Dockerfile                                                    0.0s
 => => transferring dockerfile: 1.66kB                                                                         0.0s
 => [server] resolve image config for docker-image://docker.io/docker/dockerfile:1                             0.6s
 => CACHED [server] docker-image://docker.io/docker/dockerfile:1@sha256:93bfd3b68c109427185cd78b4779fc82b484b  0.0s
 => [server internal] load build definition from Dockerfile                                                    0.0s
 => WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 10)                                0.0s
 => [server internal] load metadata for docker.io/library/python:3.10.12-slim                                  0.4s
 => [server internal] load .dockerignore                                                                       0.0s
 => => transferring context: 671B                                                                              0.0s
 => [server base 1/5] FROM docker.io/library/python:3.10.12-slim@sha256:4d440b214e447deddc0a94de23a3d97d28dfa  0.0s
 => [server internal] load build context                                                                       0.1s
 => => transferring context: 66.09kB                                                                           0.1s
 => CACHED [server base 2/5] WORKDIR /app                                                                      0.0s
 => CACHED [server base 3/5] RUN adduser     --disabled-password     --gecos ""     --home "/nonexistent"      0.0s
 => CACHED [server base 4/5] RUN --mount=type=cache,target=/root/.cache/pip     --mount=type=bind,source=requ  0.0s
 => CACHED [server base 5/5] COPY . .                                                                          0.0s
 => [server] exporting to image                                                                                0.0s
 => => exporting layers                                                                                        0.0s
 => => writing image sha256:30a944cf3e205b5fe271e2bdc6f3b223f788035daaecfd2108bfaa75be2837f4                   0.0s
 => => naming to docker.io/library/exercise-python-application-server                                          0.0s
 => [server] resolving provenance for metadata file                                                            0.0s
[+] Running 1/0
 ✔ Container exercise-python-application-server-1  Created                                                     0.0s
Attaching to server-1
server-1  |  * Serving Flask app 'app'
server-1  |  * Debug mode: off
server-1  | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
server-1  |  * Running on all addresses (0.0.0.0)
server-1  |  * Running on http://127.0.0.1:5001
server-1  |  * Running on http://172.18.0.2:5001
server-1  | Press CTRL+C to quit
server-1  | 172.18.0.1 - - [12/Feb/2025 21:02:45] "GET / HTTP/1.1" 200 -
server-1  | 172.18.0.1 - - [12/Feb/2025 21:02:45] "GET /favicon.ico HTTP/1.1" 404 -
server-1  | 172.18.0.1 - - [12/Feb/2025 21:03:08] "GET / HTTP/1.1" 200 -
server-1  | 172.18.0.1 - - [12/Feb/2025 21:03:08] "GET /favicon.ico HTTP/1.1" 404 -


v View in Docker Desktop   o View Config   w Enable Watch

3f. View it in a local browser

http://localhost:5001/

Works


Remotely

http://galactica.starlabs.us:5001/

Works


Ctrl+C shutdown the app

Gracefully stopping... (press Ctrl+C again to force)
[+] Stopping 1/1
 ✔ Container exercise-python-application-server-1  Stopped

Build an image

blazingraptor@galactica:~/git/exercise-python-application$ docker build -t blazingraptor/exercise-python-application:1.0 .
[+] Building 0.8s (13/13) FINISHED                                            docker:default
 => [internal] load build definition from Dockerfile                                    0.0s
 => => transferring dockerfile: 1.66kB                                                  0.0s
 => resolve image config for docker-image://docker.io/docker/dockerfile:1               0.4s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:93bfd3b68c109427185cd78  0.0s
 => [internal] load build definition from Dockerfile                                    0.0s
 => [internal] load metadata for docker.io/library/python:3.10.12-slim                  0.2s
 => [internal] load .dockerignore                                                       0.0s
 => => transferring context: 671B                                                       0.0s
 => [base 1/5] FROM docker.io/library/python:3.10.12-slim@sha256:4d440b214e447deddc0a9  0.0s
 => [internal] load build context                                                       0.1s
 => => transferring context: 66.19kB                                                    0.0s
 => CACHED [base 2/5] WORKDIR /app                                                      0.0s
 => CACHED [base 3/5] RUN adduser     --disabled-password     --gecos ""     --home "/  0.0s
 => CACHED [base 4/5] RUN --mount=type=cache,target=/root/.cache/pip     --mount=type=  0.0s
 => CACHED [base 5/5] COPY . .                                                          0.0s
 => exporting to image                                                                  0.0s
 => => exporting layers                                                                 0.0s
 => => writing image sha256:066a6668105c866659f52693269e5d58b26723b4a9192e77e9248e2c67  0.0s
 => => naming to docker.io/blazingraptor/exercise-python-application:1.0                0.0s

 1 warning found (use docker --debug to expand):
 - JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 51)

Fix git

Make a key

ssh-keygen -t rsa -b 4096 -C "test@example.com"

It generates 2 new keys

github-blazingraptor
github-blazingraptor.pub

Move them to

~/.ssh/

Authorize them

blazingraptor@galactica:~/git/exercise-python-application$ cat ~/.ssh/config
Host github.com
  IdentityFile ~/.ssh/github-blazingraptor
  AddKeysToAgent yes

Test your connection

blazingraptor@galactica:~/git/exercise-python-application$ ssh -T git@github.com
Hi blazingraptor! You've successfully authenticated, but GitHub does not provide shell access.

Push to git

git commit -m "new stuff"
git config --global user.email "test@example.com"
git config --global user.name "Test User"
git commit -m "new stuff"
git push origin main

Update code - Push to git

blazingraptor@galactica:~/git/exercise-python-application$ git commit -m "update change"
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git restore ..." to discard changes in working directory)
        modified:   Dockerfile
        modified:   k8s/deployment.yml
        modified:   k8s/service.yml

no changes added to commit (use "git add" and/or "git commit -a")

Add it properly

blazingraptor@galactica:~/git/exercise-python-application$ git commit -m "update change"
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git restore ..." to discard changes in working directory)
        modified:   Dockerfile
        modified:   k8s/deployment.yml
        modified:   k8s/service.yml

no changes added to commit (use "git add" and/or "git commit -a")
blazingraptor@galactica:~/git/exercise-python-application$ git add Dockerfile
blazingraptor@galactica:~/git/exercise-python-application$ git add k8s/deployment.yml
blazingraptor@galactica:~/git/exercise-python-application$ git add k8s/service.yml
blazingraptor@galactica:~/git/exercise-python-application$ git commit -m "update change"

Push the image

blazingraptor@galactica:~$ docker push blazingraptor/exercise-python-application:1.0
The push refers to repository [docker.io/blazingraptor/exercise-python-application]
a6f30f13c95b: Pushed
0bd28b033959: Pushed
5b50daaf37d9: Pushed
ffa224b85f68: Pushed
54512d0df01f: Mounted from library/python
43574d3b4af9: Mounted from library/python
1b9e3cebd93c: Mounted from library/python
5b60283f3630: Mounted from library/python
511780f88f80: Mounted from library/python
1.0: digest: sha256:ff3360982c4e0097d6285a56127d4f2c1b44aa4fb3aab9f238f4198299f72b16 size: 2206