diff --git a/content/language/python/containerize.md b/content/language/python/containerize.md index 9c4d169862..95fbe4b44e 100644 --- a/content/language/python/containerize.md +++ b/content/language/python/containerize.md @@ -18,12 +18,12 @@ This section walks you through containerizing and running a Python application. ## Get the sample application -The sample application uses the popular [Flask](https://flask.palletsprojects.com/) framework. +The sample application uses the popular [FastAPI](https://fastapi.tiangolo.com) framework. Clone the sample application to use with this guide. Open a terminal, change directory to a directory that you want to work in, and run the following command to clone the repository: ```console -$ git clone https://github.com/docker/python-docker +$ git clone https://github.com/estebanx64/python-docker-example ``` ## Initialize Docker assets @@ -35,9 +35,9 @@ feature to help streamline the process, or you can manually create the assets. {{< tabs >}} {{< tab name="Use Docker Init" >}} -Inside the `python-docker` directory, run the `docker init` command. `docker +Inside the `python-docker-example` directory, run the `docker init` command. `docker init` provides some default configuration, but you'll need to answer a few -questions about your application. For example, this application uses Flask to +questions about your application. For example, this application uses FastAPI to run. Refer to the following example to answer the prompts from `docker init` and use the same answers for your prompts. @@ -56,7 +56,66 @@ Let's get started! ? What application platform does your project use? Python ? What version of Python do you want to use? 3.11.4 ? What port do you want your app to listen on? 8000 -? What is the command to run your app? python3 -m flask run --host=0.0.0.0 --port=8000 +? What is the command to run your app? python3 -m uvicorn app:app --host=0.0.0.0 --port=8000 +``` + +Create a file named `.gitignore` with the following contents. + +```text {collapse=true,title=".gitignore"} +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ ``` {{< /tab >}} @@ -118,7 +177,7 @@ COPY . . EXPOSE 8000 # Run the application. -CMD python3 -m flask run --host=0.0.0.0 --port=8000 +CMD python3 -m uvicorn app:app --host=0.0.0.0 --port=8000 ``` Create a file named `compose.yaml` with the following contents. @@ -139,39 +198,6 @@ services: context: . ports: - 8000:8000 - -# The commented out section below is an example of how to define a PostgreSQL -# database that your application can use. `depends_on` tells Docker Compose to -# start the database before your application. The `db-data` volume persists the -# database data between container restarts. The `db-password` secret is used -# to set the database password. You must create `db/password.txt` and add -# a password of your choosing to it before running `docker compose up`. -# depends_on: -# db: -# condition: service_healthy -# db: -# image: postgres -# restart: always -# user: postgres -# secrets: -# - db-password -# volumes: -# - db-data:/var/lib/postgresql/data -# environment: -# - POSTGRES_DB=example -# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password -# expose: -# - 5432 -# healthcheck: -# test: [ "CMD", "pg_isready" ] -# interval: 10s -# timeout: 5s -# retries: 5 -# volumes: -# db-data: -# secrets: -# db-password: -# file: db/password.txt ``` Create a file named `.dockerignore` with the following contents. @@ -212,17 +238,77 @@ Create a file named `.dockerignore` with the following contents. LICENSE README.md ``` +Create a file named `.gitignore` with the following contents. + +```text {collapse=true,title=".gitignore"} +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +``` + {{< /tab >}} {{< /tabs >}} -You should now have the following contents in your `python-docker` +You should now have the following contents in your `python-docker-example` directory. ```text -├── python-docker/ +├── python-docker-example/ │ ├── app.py │ ├── requirements.txt │ ├── .dockerignore +│ ├── .gitignore │ ├── compose.yaml │ ├── Dockerfile │ └── README.md @@ -231,25 +317,26 @@ directory. To learn more about the files, see the following: - [Dockerfile](../../reference/dockerfile.md) - [.dockerignore](../../reference/dockerfile.md#dockerignore-file) + - [.gitignore](https://git-scm.com/docs/gitignore) - [compose.yaml](../../compose/compose-file/_index.md) ## Run the application -Inside the `python-docker` directory, run the following command in a +Inside the `python-docker-example` directory, run the following command in a terminal. ```console $ docker compose up --build ``` -Open a browser and view the application at [http://localhost:8000](http://localhost:8000). You should see a simple Flask application. +Open a browser and view the application at [http://localhost:8000](http://localhost:8000). You should see a simple FastAPI application. In the terminal, press `ctrl`+`c` to stop the application. ### Run the application in the background You can run the application detached from the terminal by adding the `-d` -option. Inside the `python-docker` directory, run the following command +option. Inside the `python-docker-example` directory, run the following command in a terminal. ```console @@ -258,7 +345,9 @@ $ docker compose up --build -d Open a browser and view the application at [http://localhost:8000](http://localhost:8000). -You should see a simple Flask application. +To see the OpenAPI docs you can go to [http://localhost:8000/docs](http://localhost:8000/docs). + +You should see a simple FastAPI application. In the terminal, run the following command to stop the application. diff --git a/content/language/python/deploy.md b/content/language/python/deploy.md index de964d8a3d..7048c1b2f9 100644 --- a/content/language/python/deploy.md +++ b/content/language/python/deploy.md @@ -6,7 +6,7 @@ description: Learn how to develop locally using Kubernetes ## Prerequisites -- Complete all the previous sections of this guide, starting with [Containerize a Python application](containerize.md). +- Complete all the previous sections of this guide, starting with [Use containers for python development](develop.md). - [Turn on Kubernetes](/desktop/kubernetes/#install-and-turn-on-kubernetes) in Docker Desktop. ## Overview @@ -15,12 +15,85 @@ In this section, you'll learn how to use Docker Desktop to deploy your applicati ## Create a Kubernetes YAML file -In your `python-docker-dev` directory, create a file named -`docker-python-kubernetes.yaml`. Open the file in an IDE or text editor and add +In your `python-docker-dev-example` directory, create a file named `docker-postgres-kubernetes.yaml`. Open the file in an IDE or text editor and add the following contents. Replace `DOCKER_USERNAME/REPO_NAME` with your Docker username and the name of the repository that you created in [Configure CI/CD for your Python application](configure-ci-cd.md). +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: example + - name: POSTGRES_USER + value: postgres + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: POSTGRES_PASSWORD + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: default +spec: + ports: + - port: 5432 + selector: + app: postgres +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc + namespace: default +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret + namespace: default +type: Opaque +data: + POSTGRES_PASSWORD: cG9zdGdyZXNfcGFzc3dvcmQ= # Base64 encoded password (e.g., 'postgres_password') +``` + +In your `python-docker-dev-example` directory, create a file named `docker-python-kubernetes.yaml`. + ```yaml apiVersion: apps/v1 kind: Deployment @@ -31,19 +104,32 @@ spec: replicas: 1 selector: matchLabels: - service: flask + service: fastapi template: metadata: labels: - service: flask + service: fastapi spec: containers: - - name: flask-service - image: DOCKER_USERNAME/REPO_NAME - imagePullPolicy: Always - env: - - name: POSTGRES_PASSWORD - value: mysecretpassword + - name: fastapi-service + image: technox64/python-docker-dev-example-test:latest + imagePullPolicy: Always + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: POSTGRES_PASSWORD + - name: POSTGRES_USER + value: postgres + - name: POSTGRES_DB + value: example + - name: POSTGRES_SERVER + value: postgres + - name: POSTGRES_PORT + value: "5432" + ports: + - containerPort: 8001 --- apiVersion: v1 kind: Service @@ -53,38 +139,60 @@ metadata: spec: type: NodePort selector: - service: flask + service: fastapi ports: - port: 8001 targetPort: 8001 nodePort: 30001 ``` -In this Kubernetes YAML file, there are two objects, separated by the `---`: +In these Kubernetes YAML file, there are various objects, separated by the `---`: - A Deployment, describing a scalable group of identical pods. In this case, you'll get just one replica, or copy of your pod. That pod, which is described under `template`, has just one container in it. The container is created from the image built by GitHub Actions in [Configure CI/CD for your Python application](configure-ci-cd.md). + - A Service, which will define how the ports are mapped in the containers. + - A PersistentVolumeClaim, to define a storage that will be persistent through restarts for the database. + - A Secret, Keeping the database password as a example using secret kubernetes resource. - A NodePort service, which will route traffic from port 30001 on your host to port 8001 inside the pods it routes to, allowing you to reach your app from the network. To learn more about Kubernetes objects, see the [Kubernetes documentation](https://kubernetes.io/docs/home/). +> **Note** +> +> * The `NodePort` service is good for development/testing purposes. For production you should implement an [ingress-controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/). + ## Deploy and check your application -1. In a terminal, navigate to `python-docker-dev` and deploy your application to +1. In a terminal, navigate to `python-docker-dev-example` and deploy your database to Kubernetes. ```console - $ kubectl apply -f docker-python-kubernetes.yaml + $ kubectl apply -f docker-postgres-kubernetes.yaml ``` You should see output that looks like the following, indicating your Kubernetes objects were created successfully. - ```shell + ```console + deployment.apps/postgres created + service/postgres created + persistentvolumeclaim/postgres-pvc created + secret/postgres-secret created + ``` + + Now, deploy your python application. + + ```console + kubectl apply -f docker-python-kubernetes.yaml + ``` + + You should see output that looks like the following, indicating your Kubernetes objects were created successfully. + + ```console deployment.apps/docker-python-demo created service/service-entrypoint created ``` @@ -97,9 +205,10 @@ To learn more about Kubernetes objects, see the [Kubernetes documentation](https Your deployment should be listed as follows: - ```shell + ```console NAME READY UP-TO-DATE AVAILABLE AGE - docker-python-demo 1/1 1 1 15s + docker-python-demo 1/1 1 1 48s + postgres 1/1 1 1 2m39s ``` This indicates all one of the pods you asked for in your YAML are up and running. Do the same check for your services. @@ -110,13 +219,14 @@ To learn more about Kubernetes objects, see the [Kubernetes documentation](https You should get output like the following. - ```shell - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - kubernetes ClusterIP 10.96.0.1 443/TCP 23h - service-entrypoint NodePort 10.99.128.230 8001:30001/TCP 75s + ```console + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kubernetes ClusterIP 10.43.0.1 443/TCP 13h + postgres ClusterIP 10.43.209.25 5432/TCP 3m10s + service-entrypoint NodePort 10.43.67.120 8001:30001/TCP 79s ``` - In addition to the default `kubernetes` service, you can see your `service-entrypoint` service, accepting traffic on port 30001/TCP. + In addition to the default `kubernetes` service, you can see your `service-entrypoint` service, accepting traffic on port 30001/TCP and the internal `ClusterIP` `postgres` with the port `5432` open to accept connections from you python app. 3. In a terminal, curl the service. Note that a database was not deployed in this example. @@ -126,10 +236,11 @@ To learn more about Kubernetes objects, see the [Kubernetes documentation](https Hello, Docker!!! ``` -4. Run the following command to tear down your application. +4. Run the following commands to tear down your application. ```console $ kubectl delete -f docker-python-kubernetes.yaml + $ kubectl delete -f docker-postgres-kubernetes.yaml ``` ## Summary diff --git a/content/language/python/develop.md b/content/language/python/develop.md index 494da50b3e..713f3a120d 100644 --- a/content/language/python/develop.md +++ b/content/language/python/develop.md @@ -22,7 +22,7 @@ You'll need to clone a new repository to get a sample application that includes 1. Change to a directory where you want to clone the repository and run the following command. ```console - $ git clone https://github.com/docker/python-docker-dev + $ git clone https://github.com/estebanx64/python-docker-dev-example ``` 2. In the cloned repository's directory, manually create the Docker assets or run `docker init` to create the necessary Docker assets. @@ -36,27 +36,86 @@ You'll need to clone a new repository to get a sample application that includes ```console $ 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.11.4 ? What port do you want your app to listen on? 8001 - ? What is the command to run your app? python3 -m flask run --host=0.0.0.0 --port=8001 + ? What is the command to run your app? python3 -m uvicorn app:app --host=0.0.0.0 --port=8001 ``` + Create a file named `.gitignore` with the following contents. + + ```text {collapse=true,title=".gitignore"} + # Byte-compiled / optimized / DLL files + __pycache__/ + *.py[cod] + *$py.class + + # C extensions + *.so + + # Distribution / packaging + .Python + build/ + develop-eggs/ + dist/ + downloads/ + eggs/ + .eggs/ + lib/ + lib64/ + parts/ + sdist/ + var/ + wheels/ + share/python-wheels/ + *.egg-info/ + .installed.cfg + *.egg + MANIFEST + + # Unit test / coverage reports + htmlcov/ + .tox/ + .nox/ + .coverage + .coverage.* + .cache + nosetests.xml + coverage.xml + *.cover + *.py,cover + .hypothesis/ + .pytest_cache/ + cover/ + + # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm + __pypackages__/ + + # Environments + .env + .venv + env/ + venv/ + ENV/ + env.bak/ + venv.bak/ + ``` + {{< /tab >}} {{< tab name="Manually create assets" >}} - + If you don't have Docker Desktop installed or prefer creating the assets manually, you can create the following files in your project directory. - + Create a file named `Dockerfile` with the following contents. ```dockerfile {collapse=true,title=Dockerfile} @@ -110,7 +169,7 @@ You'll need to clone a new repository to get a sample application that includes EXPOSE 8001 # Run the application. - CMD python3 -m flask run --host=0.0.0.0 --port=8001 + CMD python3 -m uvicorn app:app --host=0.0.0.0 --port=8001 ``` Create a file named `compose.yaml` with the following contents. @@ -204,6 +263,65 @@ You'll need to clone a new repository to get a sample application that includes LICENSE README.md ``` + Create a file named `.gitignore` with the following contents. + + ```text {collapse=true,title=".gitignore"} + # Byte-compiled / optimized / DLL files + __pycache__/ + *.py[cod] + *$py.class + + # C extensions + *.so + + # Distribution / packaging + .Python + build/ + develop-eggs/ + dist/ + downloads/ + eggs/ + .eggs/ + lib/ + lib64/ + parts/ + sdist/ + var/ + wheels/ + share/python-wheels/ + *.egg-info/ + .installed.cfg + *.egg + MANIFEST + + # Unit test / coverage reports + htmlcov/ + .tox/ + .nox/ + .coverage + .coverage.* + .cache + nosetests.xml + coverage.xml + *.cover + *.py,cover + .hypothesis/ + .pytest_cache/ + cover/ + + # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm + __pypackages__/ + + # Environments + .env + .venv + env/ + venv/ + ENV/ + env.bak/ + venv.bak/ + ``` + {{< /tab >}} {{< /tabs >}} @@ -217,7 +335,7 @@ In the `compose.yaml` file, you need to uncomment all of the database instructio The following is the updated `compose.yaml` file. -```yaml {hl_lines="7-36"} +```yaml {hl_lines="7-43"} services: server: build: @@ -225,6 +343,9 @@ services: ports: - 8001:8001 environment: + - POSTGRES_SERVER=db + - POSTGRES_USER=postgres + - POSTGRES_DB=example - POSTGRES_PASSWORD_FILE=/run/secrets/db-password depends_on: db: @@ -271,16 +392,18 @@ mysecretpassword Save and close the `password.txt` file. -You should now have the following contents in your `python-docker-dev` +You should now have the following contents in your `python-docker-dev-example` directory. ```text -├── python-docker-dev/ +├── python-docker-dev-example/ │ ├── db/ │ │ └── password.txt │ ├── app.py +│ ├── config.py │ ├── requirements.txt │ ├── .dockerignore +│ ├── .gitignore │ ├── compose.yaml │ ├── Dockerfile │ ├── README.Docker.md @@ -295,18 +418,50 @@ $ docker compose up --build Now test your API endpoint. Open a new terminal then make a request to the server using the curl commands: +Let's create an object with a post method + ```console -$ curl http://localhost:8001/initdb -$ curl http://localhost:8001/widgets +$ curl -X 'POST' \ + 'http://0.0.0.0:8001/heroes/' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": 1, + "name": "my hero", + "secret_name": "austing", + "age": 12 +}' ``` You should receive the following response: ```json -[] +{ + "age": 12, + "id": 1, + "name": "my hero", + "secret_name": "austing" +} ``` -The response is empty because your database is empty. +Let's make a get request with the next curl command: + +```console +curl -X 'GET' \ + 'http://0.0.0.0:8001/heroes/' \ + -H 'accept: application/json' +``` + +You should receive the same response as above because it's the only one object we have in database. + +```json +{ + "age": 12, + "id": 1, + "name": "my hero", + "secret_name": "austing" +} +``` Press `ctrl+c` in the terminal to stop your application. @@ -319,7 +474,7 @@ Watch](../../compose/file-watch.md). Open your `compose.yaml` file in an IDE or text editor and then add the Compose Watch instructions. The following is the updated `compose.yaml` file. -```yaml {hl_lines="14-17"} +```yaml {hl_lines="17-20"} services: server: build: @@ -327,6 +482,9 @@ services: ports: - 8001:8001 environment: + - POSTGRES_SERVER=db + - POSTGRES_USER=postgres + - POSTGRES_DB=example - POSTGRES_PASSWORD_FILE=/run/secrets/db-password depends_on: db: @@ -377,7 +535,7 @@ Hello, Docker! Any changes to the application's source files on your local machine will now be immediately reflected in the running container. -Open `python-docker-dev/app.py` in an IDE or text editor and update the `Hello, Docker!` string by adding a few more exclamation marks. +Open `python-docker-dev-example/app.py` in an IDE or text editor and update the `Hello, Docker!` string by adding a few more exclamation marks. ```diff - return 'Hello, Docker!'