Databases are among the most important components of modern IT infrastructure. Whether you are running an online store, a monitoring platform, an internal business application, or a customer-facing service, sooner or later you need a reliable way to store information. While deploying web applications in Docker containers has become common practice, running databases in containers still generates considerable discussion among system administrators and DevOps engineers.
Some administrators argue that databases should never run inside containers because containers are temporary by design. Others have been operating PostgreSQL, MySQL, and MariaDB in Docker environments for years without major problems. So, who is right?
In my opinion, the debate about databases in containers often focuses on the wrong question. Instead of asking whether a database should run inside a container, we should examine how its data is stored, protected, backed up, and recovered when something goes wrong.
After all, a database does not become unreliable simply because its process runs inside a container. However, poor storage configuration, missing backups, incorrect resource limits, and careless updates can certainly turn a perfectly functional database into a serious operational risk.
In this article, we will examine how containerized databases work, why persistent storage is essential, and how to run PostgreSQL with Docker. Furthermore, we will discuss performance, security, backups, database upgrades, and the challenges of operating stateful applications in Kubernetes.
Databases in Containers: Why Is This Still Controversial?
To understand the discussion, we first need to distinguish between stateless and stateful applications. Although both can run inside containers, their operational requirements are significantly different.
A stateless application does not depend on permanently storing important information inside its own container filesystem. For example, a web frontend might process incoming requests and retrieve all necessary data from an external database. If the frontend container stops, another instance can replace it without losing business-critical information.
A database, however, is fundamentally different. Its primary responsibility is to preserve information reliably, even when the application process stops or the underlying system restarts.
Consequently, the database must retain its data independently of the container responsible for running the database software.
This is where a common misunderstanding begins. Containers are often described as disposable, which is correct from an operational perspective. You should be able to replace a container without manually rebuilding its entire software environment.
Nevertheless, disposable containers do not require disposable data.
The important distinction is that the container lifecycle and the data lifecycle must be managed separately. Once you understand this principle, running databases in containers becomes much easier to evaluate.
How Does a Database Work Inside a Container?
Technically, a database running in Docker is still an ordinary database process. PostgreSQL uses its familiar database engine, MySQL processes SQL queries in the usual way, and both systems communicate with applications through their normal network protocols.
The difference is the surrounding execution environment.
Instead of installing PostgreSQL directly on the host operating system, you start a container from an image that already includes the database software and its runtime dependencies.
As a result, the installation becomes more reproducible. Different developers can use the same database image, while test environments can run a specifically selected software version without requiring a separate operating system installation.
Furthermore, containers provide process isolation through Linux kernel features such as namespaces and control groups. These mechanisms isolate certain resources and allow administrators to control memory and CPU consumption.
However, a container is not a complete virtual machine. The database still relies on the host kernel, available hardware resources, networking, and storage infrastructure.
Therefore, moving PostgreSQL into a container does not automatically improve performance, availability, or security. Instead, it standardizes how the database software is packaged and executed.
Persistent Storage: The Most Important Requirement
If there is one thing you should understand before deploying a database in Docker, it is persistent storage.
By default, a container generally has a writable filesystem layer associated with that specific container instance. Files written into this layer may survive a normal restart of the same container, but they are normally removed when the container itself is deleted.
For temporary application files, this may be perfectly acceptable. For database files containing orders, customer information, configuration records, or transaction histories, it is not.
Therefore, databases should store their important files outside the disposable writable layer of the container.
Docker provides two common approaches: named volumes and bind mounts.
Docker Volumes Explained
A Docker volume is a storage resource managed by Docker. Unlike the writable layer of a container, a named volume exists independently of an individual container instance.
Consequently, you can stop and remove a database container, create another container, and attach the same volume again.
The data remains available, provided the underlying storage is intact and the new database instance is compatible with the existing data.
Creating a volume is straightforward:
docker volume create postgres_dataAfter creating the volume, you can mount it into the location used by the database image.
However, the correct mount location depends on the database and image version. For example, the official PostgreSQL 18 image uses a different recommended volume location from PostgreSQL 17 and earlier.
For PostgreSQL 18, the named volume should be mounted at /var/lib/postgresql, while the database stores its files in a version-specific subdirectory.
This detail matters because using an outdated volume path can result in files being stored somewhere other than the location you intended.
What About Bind Mounts?
Bind mounts provide another way to store persistent data. Instead of using a Docker-managed volume, you mount a specific directory from the host filesystem into the container.
This can be useful when you need direct access to the files, dedicated storage partitions, or integration with existing host-level administration tools.
Nevertheless, bind mounts require additional attention to permissions, directory ownership, and filesystem management.
For many straightforward Docker installations, named volumes are therefore the simpler option. However, both approaches can be appropriate when configured correctly.
One thing remains important regardless of which method you choose: persistent storage is not the same as a backup.
Practical Example: Running PostgreSQL in Docker
Now that we have covered the theory, let’s deploy a PostgreSQL database inside a Docker container.
For this example, we will use the official PostgreSQL 18 image and a named Docker volume. This is intended as a local learning environment rather than a complete production configuration.
Step 1: Create the Storage Volume
First, create a named volume that will contain the PostgreSQL database files:
docker volume create postgres_dataDocker manages the volume separately from the container, allowing us to recreate the database process without automatically deleting its data.
Step 2: Start the PostgreSQL Container
Next, start PostgreSQL using the following command:
docker run -d \
--name postgres-db \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD='LocalTestPassword123!' \
-e POSTGRES_DB=appdb \
-v postgres_data:/var/lib/postgresql \
postgres:18Let’s examine the configuration.
The --name parameter gives the container a recognizable name. Additionally, the environment variables define the initial database user, password, and database name.
The -v argument mounts our named volume into PostgreSQL’s storage location.
Finally, postgres:18 identifies the PostgreSQL image that Docker should use.
Notice that we have not published port 5432 to the host network. Since we will access the database directly through Docker for this example, exposing the database service is unnecessary.
Important: The password above is only suitable as an illustrative local test value. For real environments, avoid including credentials directly in shell commands or publicly accessible configuration files.
Step 3: Verify That PostgreSQL Is Running
Once Docker has started the container, check its status:
docker psYou can also inspect PostgreSQL’s startup messages:
docker logs postgres-dbThese logs are particularly useful when PostgreSQL cannot initialize its data directory or encounters a permission problem.
Step 4: Connect to PostgreSQL
Next, open an interactive PostgreSQL session:
docker exec -it postgres-db \
psql -U appuser -d appdbOnce connected, create a simple table:
CREATE TABLE container_test (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
INSERT INTO container_test (name)
VALUES ('Docker Persistence Test');
SELECT * FROM container_test;The query should return the record we just inserted.
Although this is a very simple example, it will help us verify that PostgreSQL is storing its data persistently.
Step 5: Test Data Persistence
Leave the PostgreSQL session using \q. Then stop and remove the container:
docker stop postgres-db
docker rm postgres-dbAt this point, the PostgreSQL container no longer exists. However, our named Docker volume remains available.
We can now create another container using the same volume:
docker run -d \
--name postgres-db \
-v postgres_data:/var/lib/postgresql \
postgres:18Because the existing data directory has already been initialized, PostgreSQL uses the stored database instead of creating a new one.
Finally, verify that the record still exists:
docker exec -it postgres-db \
psql -U appuser -d appdb \
-c "SELECT * FROM container_test;"If everything was configured correctly, our original record should still be there.
This demonstrates one of the most important principles of containerized databases: the database process can be replaced while its persistent data remains available.
Managing PostgreSQL with Docker Compose
While individual Docker commands are useful for learning, they become inconvenient when an application requires several containers and additional configuration.
For example, a typical web application might include PostgreSQL, a backend API, a reverse proxy, and a background worker.
Instead of starting each component manually, Docker Compose allows you to describe the complete environment in a YAML file.
As a result, the configuration becomes easier to understand, review, and reproduce.
A simple PostgreSQL Compose configuration could look like this:
services:
postgres:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:Before starting this configuration, supply the POSTGRES_PASSWORD environment variable through an appropriate local configuration method. For production environments, use a suitable secret-management approach rather than committing credentials to version control.
After saving the file as compose.yaml, start the service with:
docker compose up -dThe configuration also includes a health check using pg_isready. This helps Docker report whether PostgreSQL is accepting connections.
However, keep in mind that a successful health check does not prove that the complete application or database infrastructure is healthy.
Database Performance Inside Containers
One of the most common concerns about databases in containers is performance.
Since database systems frequently perform large numbers of read and write operations, even small storage delays can affect application response times.
However, containerization itself does not necessarily introduce significant overhead compared with running the database directly on a Linux host.
The actual performance depends much more on the underlying storage system, available memory, CPU allocation, filesystem behavior, and database configuration.
For example, PostgreSQL running on fast local SSD storage may perform very differently from the same database using a high-latency network storage system.
Similarly, an overly restrictive container memory limit can negatively affect database caching and may even result in processes being terminated when memory consumption exceeds the configured limit.
Therefore, resource limits should be based on realistic measurements rather than arbitrary assumptions.
Furthermore, you should monitor storage latency, disk throughput, query execution times, and database connection usage.
If performance becomes a problem, investigate these metrics before assuming that Docker is responsible.
Why Backups Matter More Than the Container Runtime
A database may run perfectly in Docker and still be operationally unsafe if it lacks a reliable backup strategy.
This is perhaps the most important lesson when working with containerized databases.
Although a Docker volume protects data from being removed together with the container, it does not protect against every possible failure.
For example, the underlying disk could fail, the filesystem could become corrupted, or an administrator could accidentally delete important tables.
Consequently, persistent storage must always be combined with independent backups.
Creating a PostgreSQL Backup
For a small PostgreSQL database, you can create a logical backup with pg_dump.
docker exec postgres-db \
pg_dump -U appuser -d appdb \
> backup.sqlThe database export is written to a file on the machine where the command is executed.
For a simple test, this is a useful starting point. However, production backup procedures should also consider retention periods, encryption, off-site storage, monitoring, and recovery objectives.
Restoring a Backup
A plain SQL backup can be restored into an appropriate target database using psql.
docker exec -i postgres-db \
psql -U appuser -d appdb \
< backup.sqlIdeally, test the recovery in a separate database or isolated environment rather than importing the backup into an existing production database.
Furthermore, larger PostgreSQL installations may benefit from physical backup tools, WAL archiving, and point-in-time recovery.
These mechanisms can provide more flexible recovery options, especially when downtime and data loss must be minimized.
A backup is only truly useful when you have verified that it can be restored successfully.
Running MySQL and MariaDB in Containers
Although PostgreSQL is a convenient example, the same general principles also apply to MySQL and MariaDB.
Both databases are widely available through container images and can be used in Docker-based development and production environments.
However, you should not assume that all database images use the same configuration variables or data directories.
For example, MySQL commonly stores database files under /var/lib/mysql, while PostgreSQL uses a different directory structure.
Initialization procedures, upgrade requirements, and backup tools can also differ.
Therefore, always consult the documentation for the exact image and database version you intend to use.
Regardless of the database engine, the fundamental architecture remains similar: the container provides the runtime environment, while persistent storage protects the data across container replacements.
Databases in Kubernetes: A Good Idea?
Running a single database container with Docker is relatively straightforward. However, operating databases inside Kubernetes introduces additional considerations.
Kubernetes was designed to manage containerized applications through declarative configurations and controllers.
For stateless workloads, this model works particularly well. If an application Pod disappears, Kubernetes can create another Pod to replace it.
However, databases require more than simply restarting an application process.
Their storage must remain available, data must remain consistent, and some database architectures require stable identities for individual instances.
Fortunately, Kubernetes provides resources specifically designed for stateful applications.
Understanding Kubernetes StatefulSets
A StatefulSet is a Kubernetes workload resource that manages Pods with stable identities.
Unlike ordinary interchangeable application replicas, StatefulSet Pods can maintain predictable names and associated storage identities across replacements.
This is useful for database systems in which individual instances require their own persistent storage.
Furthermore, StatefulSets can use volume claim templates to create separate storage claims for individual Pods.
Nevertheless, a StatefulSet alone does not automatically create a highly available database cluster.
For example, deploying three independent PostgreSQL containers does not magically provide replication, consistent failover, or a shared database state.
Those capabilities must be implemented through the database system itself or suitable management software.
PersistentVolumes and PersistentVolumeClaims
Kubernetes separates applications from their underlying storage infrastructure through PersistentVolumes and PersistentVolumeClaims.
A PersistentVolume represents storage available to the cluster, while a PersistentVolumeClaim describes the storage requirements of an application.
Additionally, StorageClasses can define how new storage volumes are provisioned dynamically.
This abstraction is useful because applications do not necessarily need to know the exact details of the storage backend.
However, the reliability of a database still depends on that backend.
If a storage system becomes unavailable, Kubernetes cannot simply restore missing data by creating a replacement Pod.
Therefore, storage architecture deserves as much attention as the database configuration itself.
Database Operators: Automating Complex Tasks
One increasingly popular approach to running databases in Kubernetes is the use of Operators.
A Kubernetes Operator extends the platform with application-specific management logic.
For databases, that may include automated initialization, replication management, failover procedures, and backup integration.
For example, CloudNativePG is a Kubernetes Operator designed to manage PostgreSQL workloads.
Instead of manually configuring every database instance, administrators can describe the desired database configuration through Kubernetes resources.
The Operator then performs supported management tasks to maintain that configuration.
However, even a sophisticated Operator does not eliminate the need for database expertise.
Administrators must still understand replication, storage behavior, backup retention, database recovery, and the consequences of infrastructure failures.
Consequently, Operators should be treated as automation tools rather than substitutes for a reliable operational strategy.
Security Best Practices for Containerized Databases
Security is another area where databases in containers require careful planning.
Although Docker and Kubernetes provide isolation mechanisms, you should never assume that a database is secure simply because it runs inside a container.
One common mistake is publishing a database port unnecessarily.
For example, if an application and PostgreSQL share a private Docker network, the application can usually connect directly using the database service name.
There is often no reason to expose PostgreSQL to the public internet.
Furthermore, database credentials should not be embedded in container images or stored in publicly accessible repositories.
Other important measures include:
- Using trusted and regularly updated container images.
- Running database processes with minimal necessary privileges.
- Restricting network access to authorized applications.
- Protecting Docker volumes and host storage permissions.
- Encrypting sensitive connections when required.
- Using separate application accounts with limited database permissions.
- Monitoring database access and suspicious activity.
- Protecting backups with appropriate access controls and encryption.
Additionally, Kubernetes environments should use appropriate RBAC permissions, network policies, and Pod security settings.
Security is therefore not a feature of a single container image. It is the result of multiple controls working together.
Database Updates: Why Changing the Image Is Not Always Enough
One of the most attractive features of containers is the ability to replace application instances with new versions.
However, database upgrades require special attention because the underlying data format may change between major releases.
For example, upgrading PostgreSQL from one major version to another may require a supported migration process rather than simply changing the Docker image tag.
This is particularly important when reusing an existing persistent volume.
Therefore, before performing a major database upgrade, verify the supported upgrade procedure, create a tested backup, and evaluate application compatibility.
For PostgreSQL, depending on the situation, an upgrade may involve tools such as pg_upgrade or a logical export and import.
Furthermore, production environments should avoid relying blindly on moving image tags when reproducibility is important.
Pinning an approved image version or digest makes deployments more predictable.
Ultimately, containerization simplifies packaging, but it does not remove the operational responsibilities associated with database lifecycle management.
When Would I Run a Database in a Container?
For local development environments, I would generally consider containerized databases an excellent option.
Developers can start PostgreSQL, MySQL, or MariaDB without installing the database software directly on their operating systems.
Moreover, multiple projects can use different database versions without interfering with one another.
Containerized databases are also useful in automated integration tests, temporary test environments, and CI pipelines.
In production, however, I would make the decision more carefully.
For a small application with moderate traffic, a PostgreSQL container running with reliable storage, proper monitoring, and regular backups can be a reasonable solution.
For a business-critical application with strict recovery objectives, high transaction volumes, and demanding availability requirements, the infrastructure needs more detailed evaluation.
In some cases, Kubernetes with a suitable database Operator may be the right choice.
In other situations, a database running on a virtual machine or a managed database service may be operationally simpler.
There is also nothing wrong with running your application containers in Kubernetes while keeping the database outside the cluster.
In fact, separating application orchestration from database operations can be a sensible architectural decision.
Common Mistakes When Running Databases in Containers
Many problems associated with containerized databases are caused by configuration mistakes rather than the container technology itself.
For example, storing important database files only in the container’s writable filesystem layer can cause data loss when the container is recreated.
Similarly, relying exclusively on a Docker volume without maintaining independent backups can create a false sense of security.
Another common problem is assigning overly restrictive memory limits without understanding the database workload.
Furthermore, upgrading database images without checking compatibility can result in failed startups or migration problems.
Unnecessary exposure of database ports, weak credentials, and missing monitoring are additional risks.
Fortunately, most of these problems can be avoided through clear configuration standards, tested recovery procedures, and appropriate operational documentation.
Frequently Asked Questions About Databases in Containers
Is It Safe to Run PostgreSQL in Docker?
Yes, PostgreSQL can be operated inside Docker when storage, permissions, networking, backups, and updates are configured correctly. However, Docker alone does not provide a complete security or high-availability solution.
Will I Lose Data When Restarting a Database Container?
A normal restart does not automatically delete the data stored in the same container. However, removing and recreating a container may result in data loss if the data was stored only in its writable layer. Therefore, persistent storage is essential.
Are Databases Slower in Containers?
Not necessarily. Database performance depends on the storage backend, CPU and memory allocation, network latency, and configuration. Containerization itself does not automatically cause significant performance degradation.
Is a Docker Volume the Same as a Backup?
No. A Docker volume provides persistent storage independently of an individual container. However, it does not automatically protect against hardware failures, corrupted files, or accidental deletion. Separate backups remain necessary.
Should I Run Databases in Kubernetes?
Kubernetes can run databases using StatefulSets, persistent storage, and specialized Operators. Nevertheless, the decision should depend on available expertise, reliability requirements, and the operational complexity of the database.
Is a Database Better in Docker or a Virtual Machine?
Both approaches can be appropriate. Docker offers standardized packaging and efficient deployment, while virtual machines provide complete operating system environments and a different isolation boundary. The best choice depends on the actual application and infrastructure requirements.
Conclusion: Databases in Containers Are Not the Real Problem
Running databases in containers is neither automatically dangerous nor universally the best solution.
Containers provide a convenient and reproducible way to package database software. However, the reliability of the database still depends on storage, backup procedures, security, monitoring, and maintenance.
With Docker volumes, PostgreSQL and MySQL can retain their data independently of individual container instances. Furthermore, Docker Compose provides a structured way to manage configurations and related services.
Kubernetes introduces additional possibilities through StatefulSets, PersistentVolumeClaims, and database Operators. Nevertheless, these features also introduce operational complexity that should not be underestimated.
For learning and development, I would strongly recommend experimenting with a small PostgreSQL container. Create a volume, insert some data, remove the container, and start a replacement using the same storage.
Afterward, create a backup and restore it into a separate test environment. This exercise will teach you more about reliable database operations than simply starting a container and checking whether it runs.
For production workloads, however, begin with the business requirements. Determine how much downtime is acceptable, how much data could be lost during an incident, and how quickly the database must be restored.
Only then should you choose between Docker, Kubernetes, traditional virtual machines, or a managed database platform.
My conclusion is simple: The container is rarely the biggest risk. The real risk is running a database without a well-designed storage, security, and recovery strategy.
Once these fundamentals are addressed, databases in containers can be a practical and reliable part of modern IT infrastructure.
Further Reading
For more technical information, take a look at the official Docker documentation about volumes.
The official PostgreSQL Docker image documentation also explains important version-specific storage settings.
Finally, if you want to explore stateful workloads in Kubernetes, I recommend the Kubernetes StatefulSet documentation.