DEV Community

João Vitor
João Vitor

Posted on • Edited on

Docker for beginners: Creating Database Containers

In this article, I will provide some examples of how to create database containers using Docker.

The focus of this article is to assist beginners in using Docker in their projects, addressing questions about creating docker-compose or Dockerfile.

As a reference for the most popular databases, I used the top 9 from the Stack Overflow survey of 2023.

Summary

PostgreSQL

In our Dockerfile, it will be quite simple. We will use the PostgreSQL image in version 16 and expose port 5432 so that it is accessible by our container.

FROM postgres:16

EXPOSE 5432
Enter fullscreen mode Exit fullscreen mode

Let’s add the database container to our services and assign a name to it, in this case, postgres_db.

services:
  database:
    container_name: postgres_db
Enter fullscreen mode Exit fullscreen mode

In the build tag, we will indicate where our Dockerfile is located, which will be used to configure our Postgres container.

    build:
      context: .
      dockerfile: Dockerfile.postgres
Enter fullscreen mode Exit fullscreen mode

In the context, we use the dot to indicate that the Dockerfile is located in the same directory as the docker-compose.

As PostgreSQL uses the default user postgres, we will only add a password for our user in the environment section.

    environment:
      POSTGRES_PASSWORD: 12345678
Enter fullscreen mode Exit fullscreen mode

Now we need to add the communication ports between the host machine and the container, this will create a connection between the two, making it possible to access the database that was created in the container.

    ports:
      - "5432:5432"
Enter fullscreen mode Exit fullscreen mode

Creating volumes is optional, but it's an interesting thing to do when we're using databases. It allows for data persistence in our container, and even if it's destroyed, we can recreate the container with the data intact from the volume. This is a useful feature for maintaining data consistency and durability.

    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
    postgres_data:
Enter fullscreen mode Exit fullscreen mode

Finally, we will create a Network. They are used to facilitate communication between our services. For example, let’s say you want to create a Compose that includes Postgres and pgAdmin. To facilitate interaction between them, we add both to the same network to assist in communication (Example here).

    networks:
      - mynet

networks:
  mynet:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Docker-compose

version: '3.8'

services:
  database:
    container_name: postgres_db
    build:
      context: .
      dockerfile: Dockerfile.postgres
    environment:
      POSTGRES_PASSWORD: 12345678
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - mynet

volumes:
  postgres_data:

networks:
  mynet:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

MySQL

For the MySQL Dockerfile, we will make the same configuration as PostgreSQL, with the difference that we will expose the default port of MySQL.

FROM mysql:8.3

EXPOSE 3306
Enter fullscreen mode Exit fullscreen mode

The docker-compose will follow the same structure as the Postgres compose, with the difference that in the environment section more fields will be added besides the user’s password.

    environment:
      MYSQL_ROOT_PASSWORD: 12345678
      MYSQL_DATABASE: mydb
      MYSQL_USER: admin
      MYSQL_PASSWORD: admin123
Enter fullscreen mode Exit fullscreen mode

The MYSQL_ROOT_PASSWORD field, as the name suggests, will be the password for the root user. Do not confuse it with the MYSQL_PASSWORD field, which is the password for the user defined in the MYSQL_USER field. The MYSQL_DATABASE field is the name of the database that will be created when building the container. You can create other databases after the container is created using MySQL commands or a database client, such as DBeaver (Example here).

Docker-compose

version: '3.8'

services:
  database:
    container_name: mysql_db
    build:
      context: .
      dockerfile: Dockerfile.mysql
    environment:
      MYSQL_ROOT_PASSWORD: 12345678
      MYSQL_DATABASE: mydb
      MYSQL_USER: admin
      MYSQL_PASSWORD: admin123
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - mynet2

volumes:
  mysql_data:

networks:
  mynet2:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

SQLite

SQLite is different from the other databases mentioned here. It is a disk-based database, which means it stores the data generated in a file on the system itself and does not depend on a server, like the other databases presented. Generally, the files are saved with the .db extension.

It is widely used in mobile applications, mainly on Android, due to its presence on many devices.

Dockerfile

For the SQLite Dockerfile, we will use an image of a Linux distribution called Alpine, known for its lightness, which makes it a great option for Docker containers.

FROM alpine:3.19

RUN apk add --no-cache sqlite

WORKDIR /database
Enter fullscreen mode Exit fullscreen mode

In this Dockerfile, we install SQLite in our container and create a directory called "database", where we will execute our SQLite commands and save the databases created.

Docker-compose

The SQLite Compose will follow a structure similar to that of Postgres and MySQL, but with some differences.

The first difference is in the volumes, where we will specify the directory in which the volume will be mounted, allowing for more efficient manipulation of the container data.

    volumes:
      - ./sqlite_data:/database
Enter fullscreen mode Exit fullscreen mode

The second difference is the tail -f /dev/null command. Since SQLite is a disk-based database and does not have a server to keep the container active, we use this command to keep the container running.

command: ["tail", "-f", "/dev/null"]
Enter fullscreen mode Exit fullscreen mode

The tail command is used to display the last lines of a file, while the -f argument is used to specify the file that the command should follow waiting for new lines. By specifying /dev/null, which contains nothing, the command will not display any content in the terminal, thus keeping our container active so that we can use the SQLite commands in the terminal.

version: '3.8'

services:
  database:
    container_name: sqlite_db
    build:
      context: .
      dockerfile: Dockerfile.sqlite
    volumes:
      - ./sqlite_data:/database
    networks:
      - mynet3
    command: ["tail", "-f", "/dev/null"]

volumes:
  sqlite_data:

networks:
  mynet3:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

MongoDB

MongoDB is a NoSQL database, unlike the others presented so far, which were SQL, that is, relational databases. Mongo is a document-oriented database and is widely used for its flexibility, scalability, and ease of manipulation.

Dockerfile

Our Dockerfile will follow the same pattern as the others. We will use the latest version and we will expose the default port of MongoDB, which is 27017.

FROM mongo:latest

EXPOSE 27017
Enter fullscreen mode Exit fullscreen mode

Docker-compose

In the MongoDB docker-compose, we will provide two environment variables. The first is MONGO_INITDB_ROOT_USERNAME, to define the root user, and the second is MONGO_INITDB_ROOT_PASSWORD, where we will add the password for this user.

    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: 12345678
Enter fullscreen mode Exit fullscreen mode

We will also use mongo-express, which is a web interface to facilitate the manipulation of Mongo, similar to PgAdmin for PostgreSQL.

mongo-express:
    image: mongo-express
Enter fullscreen mode Exit fullscreen mode

To function correctly, we need to inform in the mongo-express variables the user and password that we created for MongoDB, in addition to the database URL.

    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: 12345678
      ME_CONFIG_MONGODB_URL: mongodb://admin:12345678@database:27017/
Enter fullscreen mode Exit fullscreen mode

The structure of the URL is as follows: <Driver>://<user>:<password>@<host>:<port>/. As we are using the URL externally, we will use the name of our MongoDB service, which is database. If you are going to make the connection inside the Mongo container using mongosh, you will have to replace the host with localhost.

To be able to access mongo-express, it will be necessary to create a username and password. This will be done through the ME_CONFIG_BASICAUTH_USERNAME and ME_CONFIG_BASICAUTH_PASSWORD variables.

      ME_CONFIG_BASICAUTH_USERNAME: mongo
      ME_CONFIG_BASICAUTH_PASSWORD: 123456
Enter fullscreen mode Exit fullscreen mode

It is necessary for the services to use the same network to facilitate communication between them.

version: '3.8'

services:
  database:
    container_name: mongo_db
    build:
      context: .
      dockerfile: Dockerfile.mongo
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: 12345678
    ports:
      - "27017:27017"
    volumes:
      - mongo_data:/data/db
    networks:
      - mynet4

  mongo-express:
    image: mongo-express 
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: 12345678
      ME_CONFIG_MONGODB_URL: mongodb://admin:12345678@database:27017/
      ME_CONFIG_BASICAUTH_USERNAME: mongo
      ME_CONFIG_BASICAUTH_PASSWORD: 123456
    depends_on:
      - database
    ports:
      - "8081:8081"
    networks:
      - mynet4

volumes:
  mongo_data:

networks:
  mynet4:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Microsoft SQL Server

The SQL Server, unlike other databases, has its image maintained by the Microsoft Container Registry, instead of the Docker Hub.

Dockerfile

The image used is the Linux version of SQL Server. For more information or to use a specific database image, access here.

FROM mcr.microsoft.com/mssql/server:2022-latest

EXPOSE 1433
Enter fullscreen mode Exit fullscreen mode

Docker-compose

Unlike other banks, it is necessary to use the ACCEPT_EULA variable to confirm that you accept the terms of the End User License Agreement. And in the MSSQL_SA_PASSWORD variable, it is necessary to follow Microsoft's password policy (see here).

version: '3.8'

services:
  database:
    container_name: sqlserver_db
    build: 
      context: .
      dockerfile: Dockerfile.sqlserver
    environment:
      ACCEPT_EULA: Y
      MSSQL_SA_PASSWORD: Admin123#
    ports:
      - "1455:1433"
    networks:
      - mynet5

networks:
  mynet5:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

For more information about the image, instructions, and how to use SQL Server commands, access the official Microsoft documentation here.

Redis

Redis, like MongoDB, is also a NoSQL database. It is an in-memory database that uses a key-value data structure and is widely used for cache.

Docker-compose

The Redis Compose will be simpler. We will use the image tag to indicate which image our container will pull, and we will also expose the default Redis port, which is 6379.

    image: redis
    ports:
      - "6379:6379"
Enter fullscreen mode Exit fullscreen mode

In Redis, we can also customize its configuration through a file called redis.conf, where we can define, for example, which addresses it can connect to, how much memory will be used, specify the protocol that can be used, and many other settings. See more about Redis configurations here.

version: '3.8'

services:
  database:
    image: redis
    container_name: redis_db
    ports:
      - "6379:6379"
    networks:
      - mynet6

networks:
  mynet6:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

MariaDB

We will use version 11.2.3 of MariaDB and, like MySQL, we will expose port 3306.

FROM mariadb:11.2.3

EXPOSE 3306
Enter fullscreen mode Exit fullscreen mode

Docker-compose

The MariaDB docker-compose will be similar to MySQL's, with the only difference in the environment being that we will only swap the MYSQL prefix for MARIADB.

    environment:
      MARIADB_ROOT_PASSWORD: 13246578
      MARIADB_DATABASE: mydb
      MARIADB_USER: admin
      MARIADB_PASSWORD: admin123
Enter fullscreen mode Exit fullscreen mode

We will also have an example of docker-compose using DBeaver here.

version: '3.8'

services:
  database:
    build:
      context: .
      dockerfile: Dockerfile.mariadb
    environment:
      MARIADB_ROOT_PASSWORD: 13246578
      MARIADB_DATABASE: mydb
      MARIADB_USER: admin
      MARIADB_PASSWORD: admin123
    ports:
      - "3306:3306"
    volumes:
      - mariadb_data:/var/lib/mysql
    networks:
      - mynet7

volumes:
  mariadb_data:

networks:
  mynet7:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Elasticsearch

Elasticsearch is another NoSQL database on our list. It is widely used as a search engine, for log analysis, business analysis, and other purposes.

For a better experience, I will create a container using Elasticsearch and Kibana, which is a data visualization plugin used with Elastic. Both are part of the Elastic Stack (or ELK Stack), which includes other tools such as Beats and Logstash.

Dockerfile

In our Dockerfile, we will use version 8.12.1 of Elasticsearch. We will also create a directory where we will copy a script to execute two necessary commands to use Elastic with Kibana. Finally, we will expose the default Elasticsearch port, which is 9200.

FROM docker.elastic.co/elasticsearch/elasticsearch:8.12.1

WORKDIR /elastic

COPY setup_elasticsearch.sh /elastic

EXPOSE 9200
Enter fullscreen mode Exit fullscreen mode

In this script, we will execute two necessary commands. The first is to get the password for the elastic user, and the second command will be to generate the Kibana token. Basically, both commands are being executed and responding that we want to reset the password and generate the token.

#!/bin/bash

echo "y" | /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic

echo "y" | /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
Enter fullscreen mode Exit fullscreen mode

The use of the script is optional; you can execute the commands manually through the container terminal. But if you want to use it, just enter the container terminal and execute it by writing ./setup_elasticsearch.sh.

Docker-compose

The Compose will have some different environment variables so far. We will use only two for this example, but it is worth remembering that there are several others in the Elasticsearch and Kibana documentation.

We are using the discovery.type variable as 'single-node'. By default, Elasticsearch allows the cluster to be 'multi-node', which allows connecting and joining several nodes in the same cluster.

And the second environment variable we will use is ES_JAVA_OPTS, which defines the initial size of the heap memory that Elasticsearch can use. In this case, we are defining the size as 1GB.

version: '3.8'

services:
  elasticsearch:
    build:
      context: .
      dockerfile: Dockerfile.elastic
    container_name: Elasticsearch
    environment:
      discovery.type: "single-node"
      ES_JAVA_OPTS: "-Xms1g -Xmx1g"
    ports:
      - "9200:9200"
    networks:
      - mynet8

  kibana:
    image: docker.elastic.co/kibana/kibana:8.12.1
    container_name: kibana
    ports:
      - "5601:5601"
    networks:
      - mynet8

networks:
  mynet8:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Oracle

We are back to SQL databases, and for this example, I will use the 23c Free version of the Oracle database. Finding the Oracle image can be a bit more complicated, as like Microsoft with SQL Server, Oracle also maintains its own images, which makes it a bit harder to find and use in Docker. The version I will use can be found here.

Docker-compose

Our Compose will be simple, it will only be necessary to inform the password of our user in the environment variables.

     environment:
      ORACLE_PWD: 12345678
Enter fullscreen mode Exit fullscreen mode

You can also modify the database character set using the ORACLE_CHARACTERSET variable, but it is optional. The default value is set to AL32UTF8.

version: '3.8'

services:
  database:
    image: container-registry.oracle.com/database/free:latest
    container_name: oracle
    environment:
      ORACLE_PWD: 12345678
    ports:
      - "1521:1521"
    volumes:
      - "oracle_data:/opt/oracle/oradata"
    networks:
      - mynet9

volumes:
  oracle_data:

networks:
  mynet9:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

To learn more about this database, here are some useful links. Get Started teaches you how to install Oracle in other ways and also shows you how to connect to SQL via the terminal and in various languages. Oracle Database 23c to learn more about this version. And also the Oracle Container Registry if you want to obtain images of other Oracle products or even use other versions of the database.

Conclusion

I hope this article helps someone who is just starting out and takes some of the hassle out of creating a Docker container, as creating a database container is something we use a lot in our day-to-day lives. I'll leave here the link to the repository with all the examples.

Top comments (2)

Collapse
 
dbrower profile image
(((David Brower)))

Being security sensitive, I'm looking askance at putting admin passwords into the configurations the way this seems to be doing.

Is that really best practice?

Collapse
 
jao_dev profile image
João Vitor • Edited

It's not the best practice when thinking about something for production, what was shown in the article is more for development environments or studies.

If you want to apply it to something production-oriented or even to keep your variables safe, the most recommended thing is to use an .env file, where you can either use the env_file tag in your docker-compose or pass the variables using ${variable name}.

I recommend taking a look at this documentation here:
docs.docker.com/compose/environmen...

You can also use docker secrets (the most recommended):
docs.docker.com/engine/swarm/secrets/

docs.docker.com/compose/use-secrets/