Photo by Talyor Vick

How to Run a Local Database Using Docker

So, you are working on your side project and the time has come and you need a database? Awesome! In this article we want to show you how simple it is to start a database on your local machine, using Docker.

⚠️ Security Disclaimer

This article will not go into any detail about how to properly secure your database. The commands provided are only meant to be executed in a safe, non-production environment, like your local development machine. For ways to host a secure database, please have a look at the section "Ready to move into the cloud?" at the end of this article and do your own research.

Which database to use?

This is entirely up to you. There are a lot of different database types to choose from, and there is no right or wrong. It all depends on your specific use case.

This article includes examples for Redis and PostgreSQL. To figure out which database you should choose, please refer to other resources. I can recommend this Video: "Did I Pick The Right Database?" (1h) from Theo Browne. It provides an excellent overview of the different types of databases and when to use them.

The most common databases are relational databases (Wikipedia: Relational Database). Most of them can be managed with a domain-specific language (DSL) called "SQL" (e.g. MySQL, PostgreSQL, MariaDB). In this article we will show how to start a PostgreSQL database locally.

Another set of popular "databases" are key-value data stores (e.g. Redis, Memcached). Most often these are not used as databases, but rather as a cache for other databases. Personally, I like to use them for small side projects because of how easy they are to set up and work with. This article will provide an example of how to start Redis.

There are more types of databases like non-relational/NoSQL databases (e.g. MongoDB, CouchDB). These won't be covered in this article.

Prerequisites

Docker

If you want to follow along with the commands and code snippets in this article, you need to have Docker installed on your machine. If you are unsure whether that's the case, run the following command:

docker -v

This will print the version of your installed Docker. In case you get an error, switch to docker.com and download and install Docker.

Deno

Once we start a database, we want to verify that we can connect to it. There are a lot of ways to connect to a (local) database. In this article we are going to use the Deno JavaScript REPL (Wikipedia: REPL) to connect to the local database(s). This is entirely optional. If you want to follow along with the code snippets to verify that the database can be connected to, you need to have Deno installed. If you are unsure if that's the case, run the following command:

deno -V

This will print the version of your installed deno CLI. In case you get an error, head over to deno.land to download and install Deno.

Example: Redis

Redis is a key-value data store that uses the system memory to store data. Since it does not need to perform time-consuming I/O disk operations, it is extremely fast. The amount of data you can store in it is restricted by the size of your memory, though.

Redis comes with an official Docker image in the Docker Hub. We are going to use this image to run Redis on our local machine.

Run the following command, and you're good to go:

docker run --name some-redis --publish 6379:6379 --detach redis If everything worked, this command will respond with the container ID that it just created.

Let's have a closer look at the command itself:

  • docker run takes an image, turns it into a container, and runs that container.

  • --name some-redis assigns the name 'some-redis' to the created container. This will help to identify and control it later.

  • --publish 6379:6379 publishes the container-internal TCP port 6379 to the outside world. This allows us to connect with Redis, which is listening at port 6379 by default.

  • --detach runs the container in "detached" mode. This means that you can happily close the terminal after you ran that command. The container will continue to run.

  • redis the name of the image that we want to use. This has to be the last argument to the command, otherwise the arguments would be passed to the entrypoint of that image.

To verify that the container got created and is running, you can list your running docker containers with this command:

docker container ls

The output of this command includes information about the running containers, such as the name some-redis as well as the applied port mapping 0.0.0.0:6379->6379/tcp.

Verify that it works

Now we want to verify that we can connect to Redis and use it as a database. Like discussed in the prerequisites, we chose the Deno runtime to do this. To connect to and interact with our database we need a "driver". The Deno ecosystem provides a very convenient Redis driver, that we are going to use in our example script.

You can run this script in the Deno REPL, which you can enter by running deno in your terminal. Now either copy the below code into the REPL all at once and execute it, or execute it line by line:

// download the Deno Redis driver and all its dependencies
import { connect } from 'https://deno.land/x/redis/mod.ts';

// connect to the database
const client = await connect({ hostname: 'localhost', port: 6379 });

// store the value 'bar' at key 'foo'
await client.set('foo', 'bar'); // OK

// retrieve the value
await client.get('foo'); // 'bar'
Verify that we can connect to Redis in Deno REPL

Database drivers for Redis exist for almost all known programming languages, so feel free to search for a driver that fits your needs.

Example: PostgreSQL

Postgres (officially PostgreSQL), is a classical relational database. It is slightly less convenient to set up than Redis, but - to be fair - it's a "proper" database. If you are familiar with SQL (or want to learn it), this is a great choice. It's a well-established database with a huge community and a lot of online resources.

Postgres also comes with an official Docker image which we are going to use:

docker run --name some-postgres -p 5432:5432 -d --env POSTGRES_USER=user --env POSTGRES_PASSWORD=password postgres:alpine

Let's take a closer look at this command:

  • docker run same as for the Redis command.

  • --name some-postgres similar to the Redis command, this assigns a name to the created container.

  • -p 5432:5432 shorthand for --publish 5432:5432 (by default Postgres listens on port 5432).

  • -d shorthand for --detach.

  • --env POSTGRES_USER=user --env POSTGRES_PASSWORD=password provide environment variables that are passed to inside the container. Here we provide a default postgres username as POSTGRES_USER (will be the superuser) and a password for that user as POSTGRES_PASSWORD. These are made up, feel free to choose something else. Take a look at the linked Docker image for more details.

  • postgres the image that we want to use for the container.

Verify that it works

Like before, we will use a Deno driver and the Deno REPL to verify that we can connect to this database. Start the Deno REPL by running deno, and then execute this code in it (preferably a single command at a time):

// download the Deno Postgres driver and all its dependencies
import { Client } from "https://deno.land/x/postgres/mod.ts";

// create a client
// username and password need to match the
// ones given in the "docker run ..." command
const client = new Client({
  user: "user",
  password: "password",
  database: "user",
  hostname: "localhost",
  port: 5432,
});

// connect the client to the database
await client.connect();

// create a table 'Companies'
await client.queryObject("CREATE TABLE Companies ( Name varchar(255) )");

// insert a value
await client.queryObject("INSERT INTO Companies (Name) VALUES ('Satellytes')");

// retrieve the value
await client.queryObject("SELECT Name FROM Companies"); // { ..., rows: [{ name: 'Satellytes' }] }
Verify that we can connect to Postgres in Deno REPL

Persistence

The databases started this way will lose their data if you remove, or even stop, the containers. To persist the data, you need to mount a file on your local hard drive where the data can be stored. If you are interested, you can take a look at the Docker Storage documentation on how to do so.

Unfortunately, each database stores its data differently, so there is no easy solution that fits all scenarios. Also note, that in the case of in-memory data stores like Redis you need to dump the memory-state to a file (Redis - Persistance documentation) to not lose it.

Example: "Bind Mount" for Postgres Container

An example for storing the data from Postgres on the host system using a "bind mount", could look like this:

docker run --name some-postgres -p 5432:5432 -d --env POSTGRES_USER=user --env POSTGRES_PASSWORD=password --mount type=bind,source="$(pwd)"/postgres-data,target=/var/lib/postgresql/data postgres:alpine

This is the same command that we used before, but with one additional argument:

--mount type=bind,source="$(pwd)"/postgres-data,target=/var/lib/postgresql/data

This will mount the location at which Postgres stores its data (default: /var/lib/postgresql/data) to a folder called postgres-data in the current directory (pwd) on the host system. If you always start the Postgres container with this command, your database data will persist, even when the container got removed. Note, that this directory on the host system will be owned exclusively by the user "user" (as specified in the POSTGRES_USER environment variable), so you can't look at the file(s) in it.

Ready to move into the cloud?

You want to lift your project, including your database, up into the clouds (i.e. "deploy")? There are great websites that offer affordable "Database as a Service" (DBaaS) services. Alongside the well-known and big SaaS providers like AWS, Azure, and Cloudflare, there are smaller projects emerging that focus more on usability. I want to emphasize the following two providers, that I've been using for private projects and am very happy with:

  • planetscale.com Very fast, generous free tier

  • railway.app Free "Starter" plan in which you receive 5$ or 500 hours of usage per month (whichever limit is reached first)

These are no affiliate links, I'm just genuinely happy with their services.

Conclusion

Obviously there is a lot more to databases than what we talked about in this article. Two of the big remaining challenges are security and persistence. Depending on your application's architecture and your system's infrastructure these will need to be solved in very different ways.

However, to get started with databases, starting one on your local machine is a perfect first step. We hope that we were able to make this process less scary with this blog post, and we continue to wish you a pleasant journey 🙌

Do you want to learn more about databases and how we use them? Join our team!

Career