Use containers for development

Prerequisites

Work through the steps to build an image and run it as a containerized application in Run your image as a container.

Introduction

In this section, you’ll learn how to use volumes and networking in Docker. You’ll also use Docker to build your images and Docker Compose to make everything a whole lot easier.

First, you’ll take a look at running a database in a container and how you can use volumes and networking to persist your data and let your application to talk with the database. Then you’ll pull everything together into a Compose file which lets you to set up and run a local development environment with one command.

Run a database in a container

Instead of downloading PostgreSQL, installing, configuring, and then running the PostgreSQL database on your system directly, you can use the Docker Official Image for PostgreSQL and run it in a container.

Before you run PostgreSQL in a container, create a volume that Docker can manage to store your persistent data and configuration.

Run the following command to create your volume.

$ docker volume create db-data

Now create a network that your application and database will use to talk to each other. The network is called a user-defined bridge network and gives you a nice DNS lookup service which you can use when creating your connection string.

$ docker network create postgresnet

Now you can run PostgreSQL in a container and attach to the volume and network that you created above. Docker pulls the image from Hub and runs it for you locally. In the following command, option --mount is for starting the container with a volume. For more information, see Docker volumes.


$ docker run --rm -d \
  --mount type=volume,src=db-data,target=/var/lib/postgresql/data \
  -p 5432:5432 \
  --network postgresnet \
  --name db \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -e POSTGRES_DB=example \
  postgres
$ docker run --rm -d `
  --mount type=volume,src=db-data,target=/var/lib/postgresql/data `
  -p 5432:5432 `
  --network postgresnet `
  --name db `
  -e POSTGRES_PASSWORD=mysecretpassword `
  -e POSTGRES_DB=example `
  postgres

Now, make sure that your PostgreSQL database is running and that you can connect to it. Connect to the running PostgreSQL database inside the container.

$ docker exec -it db psql -U postgres

You should see output like the following.

psql (15.3 (Debian 15.3-1.pgdg110+1))
Type "help" for help.

postgres=#

In the previous command, you logged in to the PostgreSQL database by passing the psql command to the db container. Press ctrl-d to exit the PostgreSQL interactive terminal.

Get and run the sample application

You'll need to clone a new repository to get a sample application that includes logic to connect to the database.

  1. Change to a directory where you want to clone the repository and run the following command.

    $ git clone https://github.com/docker/python-docker-dev
    
  2. In the cloned repository's directory, run docker init to create the necessary Docker files. Refer to the following example to answer the prompts from docker init.

    $ 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
    
    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? 5000
    ? What is the command to run your app? python3 -m flask run --host=0.0.0.0
    
  3. In the cloned repository's directory, run docker build to build the image.

    $ docker build -t python-docker-dev .
    
  4. If you have any containers running from the previous sections using the name rest-server or port 8000, stop and remove them now.

  5. Run docker run with the following options to run the image as a container on the same network as the database.


    $ docker run --rm -d \
      --network postgresnet \
      --name rest-server \
      -p 8000:5000 \
      -e POSTGRES_PASSWORD=mysecretpassword \
      python-docker-dev
    
    $ docker run --rm -d `
      --network postgresnet `
      --name rest-server `
      -p 8000:5000 `
      -e POSTGRES_PASSWORD=mysecretpassword `
      python-docker-dev

  6. Test that your application is connected to the database and is able to list the widgets.

    $ curl http://localhost:8000/initdb
    $ curl http://localhost:8000/widgets
    

    You should receive the following JSON back from your service.

    []

    This is because your database is empty.

Use Compose to develop locally

When you run docker init, in addition to a Dockerfile, it also creates a compose.yaml file.

This Compose file is super convenient as you don't have to type all the parameters to pass to the docker run command. You can declaratively do that using a Compose file.

In the cloned repository's directory, open the compose.yaml file in an IDE or text editor. docker init handled creating most of the instructions, but you'll need to update it for your unique application.

In the compose.yaml file, you need to uncomment all of the database instructions. In addition, you need to add the database password as an environment variable to the server service.

The following is the updated compose.yaml file.

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker compose reference guide at
# https://docs.docker.com/compose/compose-file/

# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
  server:
    build:
      context: .
    ports:
      - 5000:5000
    environment:
      - POSTGRES_PASSWORD=mysecretpassword

# 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

Note that the file doesn't specify a network for those 2 services. Compose automatically creates a network and connects the services to it. For more information see Networking in Compose.

Before you run the application using Compose, notice that this Compose file specifies a password.txt file to hold the database's password. You must create this file as it's not included in the source repository.

In the cloned repository's directory, create a new directory named db and inside that directory create a file named password.txt that contains the password for the database. Using your favorite IDE or text editor, add the following contents to the password.txt file.

mysecretpassword

If you have any other containers running from the previous sections, stop them now.

Now, run the following docker compose up command to start your application.

$ docker compose up --build

The command passes the --build flag so Docker will compile your image and then start the containers.

Now test your API endpoint. Open a new terminal then make a request to the server using the curl commands:

$ curl http://localhost:5000/initdb
$ curl http://localhost:5000/widgets

You should receive the following response:

[]

This is because your database is empty.

Summary

In this section, you took a look at setting up your Compose file to run your Python application and database with a single command.

Related information:

Next steps

In the next section, you'll take a look at how to set up a CI/CD pipeline using GitHub Actions.