Docker for beginners: Creating Database Containers
Tags: docker, database, beginners, tutorial
Published: 2024-03-28 15:03:12

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](https://survey.stackoverflow.co/2023/#technology) survey of 2023.

## Summary

- [PostgreSQL](#postgresql)
- [MySQL](#mysql)
- [SQLite](#sqlite)
- [MongoDB](#mongodb)
- [Microsoft SQL Server](#microsoft-sql-server)
- [Redis](#redis)
- [MariaDB](#mariadb)
- [Elasticsearch](#elasticsearch)
- [Oracle](#oracle)

## 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.

```dockerfile
FROM postgres:16

EXPOSE 5432
```

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

```yml
services:
  database:
    container_name: postgres_db
```

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

```yml
    build:
      context: .
      dockerfile: Dockerfile.postgres
```

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.

```yml
    environment:
      POSTGRES_PASSWORD: 12345678
```

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.


```yml
    ports:
      - "5432:5432"
```

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.

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

volumes:
    postgres_data:
```

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](https://github.com/JoaoVitorBernardino/docker-for-beginners/blob/main/1%20-%20PostgreSQL/pgAdmin/docker-compose.yml)).

```yml
    networks:
      - mynet

networks:
  mynet:
    driver: bridge
```

### Docker-compose
```yml
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
```

## 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.

```dockerfile
FROM mysql:8.3

EXPOSE 3306
```

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.

```yml
    environment:
      MYSQL_ROOT_PASSWORD: 12345678
      MYSQL_DATABASE: mydb
      MYSQL_USER: admin
      MYSQL_PASSWORD: admin123
```

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](https://github.com/JoaoVitorBernardino/docker-for-beginners/blob/main/2%20-%20MySQL/Dbeaver/docker-compose.yml)).

### Docker-compose

```yml
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
```

## 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](https://hub.docker.com/_/alpine), known for its lightness, which makes it a great option for Docker containers.

```dockerfile
FROM alpine:3.19

RUN apk add --no-cache sqlite

WORKDIR /database
```

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.

```yml
    volumes:
      - ./sqlite_data:/database
```

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.

```yml
command: ["tail", "-f", "/dev/null"]
```

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.

```yml
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
```

## 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`.

```dockerfile
FROM mongo:latest

EXPOSE 27017
```

### 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.

```yml
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: 12345678
```

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

```yml
mongo-express:
    image: mongo-express
```

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.

```yml
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: admin
      ME_CONFIG_MONGODB_ADMINPASSWORD: 12345678
      ME_CONFIG_MONGODB_URL: mongodb://admin:12345678@database:27017/
```

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](https://www.mongodb.com/docs/mongodb-shell/), 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.

```yml
      ME_CONFIG_BASICAUTH_USERNAME: mongo
      ME_CONFIG_BASICAUTH_PASSWORD: 123456
```

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

```yml
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
```

## 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](https://mcr.microsoft.com/product/mssql/server/about).

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

EXPOSE 1433
```

### 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](https://learn.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-ver16)).

```yml
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
```

For more information about the image, instructions, and how to use SQL Server commands, access the official Microsoft documentation [here](https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker?view=sql-server-ver16&pivots=cs1-bash&tabs=cli).

## 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`.

```yml
    image: redis
    ports:
      - "6379:6379"
```

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](https://redis.io/docs/management/config-file/).

```yml
version: '3.8'

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

## MariaDB

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

```dockerfile
FROM mariadb:11.2.3

EXPOSE 3306
```

### 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`.

```yml
    environment:
      MARIADB_ROOT_PASSWORD: 13246578
      MARIADB_DATABASE: mydb
      MARIADB_USER: admin
      MARIADB_PASSWORD: admin123
```

We will also have an example of docker-compose using DBeaver [here](https://github.com/JoaoVitorBernardino/docker-for-beginners/blob/main/7%20-%20MariaDB/Dbeaver/docker-compose.yml).

```yml
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
```

## 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.

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

WORKDIR /elastic

COPY setup_elasticsearch.sh /elastic

EXPOSE 9200
```

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.

```bash
#!/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
```

> 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.

```yml
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
```

## 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](https://container-registry.oracle.com/ords/f?p=113:4:5674987054298:::4:P4_REPOSITORY,AI_REPOSITORY,AI_REPOSITORY_NAME,P4_REPOSITORY_NAME,P4_EULA_ID,P4_BUSINESS_AREA_ID:1863,1863,Oracle%20Database%20Free,Oracle%20Database%20Free,1,0&cs=3aUoFW2rXVkvlvAzDeuZi6P_iZvD9lcYmYGXWDBRN2eFu2ZZ13ezdTy9qX6C4f5agbKRr9B68yO_yzyxz87V5bA).

## Docker-compose

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

```yml
     environment:
      ORACLE_PWD: 12345678
```

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

```yml
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
```

To learn more about this database, here are some useful links. [Get Started](https://www.oracle.com/database/free/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](https://docs.oracle.com/en/database/oracle/oracle-database/23/index.html) to learn more about this version. And also the [Oracle Container Registry](https://container-registry.oracle.com/ords/f?p=113:10::::::) 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](https://github.com/JoaoVitorBernardino/docker-for-beginners) the link to the repository with all the examples.