How to Store Docker Data Locally Using Compose Bind Mounts (MongoDB Example)
3/19/2026
If you've spent enough time wrangling Docker in local development environments, you've likely hit the "phantom data" problem. You spin up a database container, insert some test records, tear it down, and suddenly realize your data is trapped in an obscure, Docker-managed named volume buried somewhere deep in /var/lib/docker/volumes/.
When working locally, visibility and portability are everything. You want your database state to live right next to your code so you can easily wipe it, inspect it, or back it up just by looking at your IDE's file tree.
Today, I'll show you the exact pattern I use to store Docker container data locally in the same directory as the docker-compose.yml file, using a practical MongoDB and Mongo Express stack.
Pro Tip: This local volume binding pattern is strictly for local development. In production environments, you should always use Docker managed named volumes or dedicated cloud storage block devices to ensure data durability, proper permissions, and backup management.
The Magic of Docker Bind Mounts
To keep our database files inside our project folder, we use a bind mount instead of a named volume. A bind mount maps a specific path on your host machine directly to a specific directory inside the container.
Let's walk through the setup using a complete MongoDB, Mongo Express, and Node.js stack.
Define the Bind Mount in Docker Compose
Create a docker-compose.yml file in your project root. Notice the ./mongo_data:/data/db line under the mongodb service. The ./ denotes the current directory where the Compose file resides.
This is the step where juniors usually mess up. By mapping the database files locally, your Git client will suddenly see thousands of binary database files as "untracked changes."
You must immediately update your .gitignore. If you accidentally commit an active database directory, you'll bloat your repository history forever and potentially leak sensitive local data.
Critical Security Step
Always ignore your local volume mount folders before running Docker Compose. Git is not designed to track raw database binaries like MongoDB's WiredTiger files.
.gitignore
# ----------------------# Node.js# ----------------------node_modules/npm-debug.log*# ----------------------# Docker / Database Data# ----------------------# IMPORTANT: Never commit your database files!mongo_data/# ----------------------# Environment Variables# ----------------------.env.env.local
Spin Up and Inspect the Local Volume
Now, bring the infrastructure online. Because we explicitly mapped the volume using a relative path, Docker will automatically create the mongo_data folder in your project root if it doesn't already exist.
Fire up the stack in detached mode:
docker-compose up -d
If you look at your file explorer now, you'll see a brand new folder called mongo_data. If you ever need to completely wipe your database (a "hard reset") and start fresh, you don't need obscure Docker volume prune commands. Just run docker-compose down, delete the folder manually, and run docker-compose up again!
Verify Persistence with a Node.js Script
Let's prove the data sticks around across container restarts. We'll use a Node.js script to connect to our local MongoDB container and insert a document.
First, install the driver via your terminal:
npm install mongodb
Next, run this connection script. Note the ?authSource=admin parameterโthis is strictly required when authenticating against a user created via Docker's MONGO_INITDB_ROOT_USERNAME environment variable.
test-mongo.js
const { MongoClient } = require("mongodb");// 1. Connection URL// We use ?authSource=admin because the Docker root user is defined in the 'admin' DBconst url = "mongodb://admin:admin123@localhost:27017?authSource=admin";const client = new MongoClient(url);// 2. Database Nameconst dbName = "myProjectDB";async function main() { try { // 3. Connect to the server await client.connect(); console.log("โ Connected successfully to MongoDB server"); const db = client.db(dbName); const collection = db.collection("documents"); // 4. Insert a document const insertResult = await collection.insertOne({ title: "Docker Guide", content: "Running MongoDB with Docker local volumes is easy.", createdAt: new Date(), }); console.log("๐ Inserted document with ID:", insertResult.insertedId); // 5. Find the document we just inserted const findResult = await collection.findOne({ _id: insertResult.insertedId, }); console.log("๐ Found document:", findResult); } catch (e) { console.error("โ Connection failed:", e); } finally { // 6. Close the connection await client.close(); console.log("๐ Connection closed"); }}main();
Run the script using node test-mongo.js.
Now, for the ultimate test: completely tear down your container stack (docker-compose down) and bring it back up. Run the script again, or visit your Mongo Express UI at http://localhost:8081 (Login: webuser / webpassword). You'll see your database state is perfectly intact, safely living inside your local ./mongo_data directory.
A Word from the Trenches: Linux Permission Gotchas
Before I wrap this up, let me save you from a headache you will run into if you are developing on a Linux host (or WSL2).
When Docker creates the ./mongo_data folder via a bind mount, it often creates it using the root user because the underlying Docker daemon runs as root. If you try to delete this folder manually from your IDE or file explorer to reset your database, your OS might hit you with a "Permission Denied" error.
If this happens, simply elevate your privileges in the terminal to clear it out:
sudo rm -rf mongo_data
Mastering your local development environment pays compounding dividends over your career. By keeping your database data visible and scoped to your project directories via Docker Compose bind mounts, you remove the "magic" from Docker and take absolute control over your local state.