Database Volumes

By the end of this exercise, you should be able to:

  • Provide a docker volume as a database backing to Postgres
  • Make one Postgres container's database available to other Postgres containers

Launching Postgres

  1. Download a postgres image, and look at its history to determine its default volume usage:

    [centos@node-0 ~]$ docker image pull postgres:9-alpine
    [centos@node-0 ~]$ docker image inspect postgres:9-alpine
    
    ...
    "Volumes": {
        "/var/lib/postgresql/data": {}
    },
    ...
    

    You should see a Volumes block like the above, indicating that those paths in the container filesystem will get volumes automatically mounted to them when a container is started based on this image.

  2. Set up a running instance of this postgres container:

    [centos@node-0 ~]$ docker container run --name some-postgres \
        -v db_backing:/var/lib/postgresql/data \
        -d postgres:9-alpine
    

    Notice the explicit volume mount, -v db_backing:/var/lib/postgresql/data; if we hadn't done this, a randomly named volume would have been mounted to the container's /var/lib/postgresql/data. Naming the volume explicitly is a best practice that will become useful when we start mounting this volume in multiple containers.

Writing to the Database

  1. The psql command line interface to postgres comes packaged with the postgres image; spawn it as a child process in your postgres container interactively, to create a postgres terminal:

    [centos@node-0 ~]$ docker container exec \
        -it some-postgres psql -U postgres
    
  2. Create an arbitrary table in the database:

    postgres=# CREATE TABLE CATICECREAM(COAT TEXT, ICECREAM TEXT);
    postgres=# INSERT INTO CATICECREAM VALUES('calico', 'strawberry');
    postgres=# INSERT INTO CATICECREAM VALUES('tabby', 'lemon');
    

    Double check you created the table you expected, and then quit this container:

    postgres=# SELECT * FROM CATICECREAM;
    
      coat  |  icecream  
    --------+------------
     calico | strawberry
     tabby  | lemon
    (2 rows)
    
    postgres=# \q
    
  3. Delete the postgres container:

    [centos@node-0 ~]$ docker container rm -f some-postgres
    
  4. Create a new postgres container, mounting the db_backing volume just like last time:

    [centos@node-0 ~]$ docker container run \
        --name some-postgres \
        -v db_backing:/var/lib/postgresql/data \
        -d postgres:9-alpine
    
  5. Reconnect a psql interface to your database, also like before:

    [centos@node-0 ~]$ docker container exec \
        -it some-postgres psql -U postgres
    
  6. List the contents of the CATICECREAM table:

    postgres=# SELECT * FROM CATICECREAM;
    

    The contents of the database have survived the deletion and recreation of the database container; this would not have been true if the database was keeping its data in the writable container layer. As above, use \q to quit from the postgres prompt.

Running Multiple Database Containers

  1. Create another postgres runtime, mounting the same backing volume:

    [centos@node-0 ~]$ docker container run \
        --name another-postgres \
        -v db_backing:/var/lib/postgresql/data \
        -d postgres:9-alpine
    
  2. Create another postgres interactive prompt, pointing at this new postgres container:

    [centos@node-0 ~]$ docker container exec \
        -it another-postgres psql -U postgres
    
  3. List the contents of the database one last time, again with SELECT * FROM CATICECREAM;. The database is readable exactly as it is from the other running database runtime, from this new postgres container.

  4. Clean up by removing all your containers and deleting your postgres volume:

    [centos@node-0 ~]$ docker container rm -f $(docker container ls -aq)
    [centos@node-0 ~]$ docker volume rm db_backing
    

Conclusion

Whenever data needs to live longer than the lifecycle of a container, it should be pushed out to a volume outside the container's filesystem; numerous popular databases are containerized using this pattern. In addition to making sure data survives container deletion, this pattern allows us to share data among multiple containers, so multiple database instances can access the same underlying data.