Skip to content

Docker

Docker is a tool that helps you package an app and everything it needs (code, libraries, settings) into one box called a container. This container can then run the same way on any computer - your laptop, your friend’s laptop, or a server on the internet.

Think of a container like a lunch box. You pack everything you need for your meal in one box, and you can carry it anywhere and eat the same meal, no matter where you are. Docker does the same thing for apps - it packs the app and everything it needs so it runs the same way everywhere.

Goes anywhere (Portability)

A container built on your computer will run the same way on any other computer that has Docker installed.

Stays separate (Isolation)

Each container runs on its own, so one app’s files or settings do not mess with another app.

Grows easily (Scalability)

Need to handle more users? Just start more containers. Need less? Just remove some.

Uses less space (Efficiency)

Containers share the same computer’s operating system, so they use fewer resources than a full virtual machine.

Easy to track versions

Docker images can be saved with version numbers, so you can go back to an older version any time you want.

  1. Install Docker: Go to the official Docker website and download Docker Desktop. Follow the steps for your computer (Windows, macOS, or Linux).

  2. Check that it installed correctly: Open your terminal (command prompt) and type:

    Terminal window
    docker --version
    # or
    docker
    # this shows basic Docker usage info and a list of commands
  3. Run a test container: To check Docker is really working, run this:

    Terminal window
    docker run hello-world

    This command downloads the hello-world image from Docker Hub and runs it, printing a message that tells you Docker is working fine.

  4. Create a Docker Hub account: Just like GitHub has repositories for code, Docker has Docker Hub for sharing images. Go to Docker Hub and make a free account so you can upload and download images.

Here are some basic commands you will use all the time.

1. docker pull <image> - Downloads an image from Docker Hub onto your computer.

Terminal window
docker pull hello-world
# or
docker pull ubuntu

2. docker images - Shows a list of all the images you already downloaded.

Terminal window
docker images

3. docker rmi <image> - Deletes an image from your computer.

Terminal window
docker rmi hello-world

4. docker run <image> - Starts (runs) a container using the image you give it.

Terminal window
docker run hello-world

5. docker run -it <image> - Starts a container and opens it in interactive mode, so you can type commands inside it.

Terminal window
docker run -it ubuntu

Once you are inside, you can run any command you like. Type exit or press Ctrl + D to leave the container.

6. docker ps - Shows all the containers that are currently running.

Terminal window
docker ps

7. docker ps -a - Shows every container, even the ones that are stopped.

Terminal window
docker ps -a

8. docker stop <container> - Stops a container that is running.

Terminal window
docker stop <container id or name>

9. docker start <container> - Starts a container that was stopped.

Terminal window
docker start <container id or name>

10. docker rm <container> - Deletes a stopped container.

Terminal window
docker rm <container id or name>

11. Pulling a specific version (tag): When you pull an image without saying which version, Docker gives you the newest (latest) one. If you want a specific version, add a colon and the version number (called a “tag”):

Terminal window
docker pull mysql:8.0

12. Running in the background (detached mode): By default, when you run a container, you see its output right in your terminal (this is called attached mode). If you want it to run quietly in the background, add the -d flag:

Terminal window
# docker run -d mysql:8.0
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
# This runs the MySQL container in the background with a root password.
# The root password here is "secret"

13. Setting environment variables: In the example above, -e sets an environment variable inside the container. This is how you configure apps that run in containers. You can add more than one -e flag if you need to set several values:

Terminal window
docker run -d -e MYSQL_ROOT_PASSWORD=secret -e MYSQL_DATABASE=mydb mysql:8.0
# This creates a MySQL container with root password "secret" and a database named "mydb".
# You can pass as many environment variables as you like this way.
# Check the documentation for the image you're using to see which ones it needs.

14. Naming your container: Use the --name flag to give your container an easy name, so it’s simpler to manage later.

Terminal window
docker run -d --name my_mysql_container -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
# This runs the MySQL container in the background, sets the root password, and names it "my_mysql_container".

15. Port mapping (port binding): Every container has its own private network. This means the ports inside a container are not open to your computer by default. To let your computer talk to a service inside a container, you need to connect (map) a port on your container to a port on your computer. This is called port forwarding.

In the example below, we connect the MySQL container’s port 3306 to port 3306 on your computer. So anything sent to port 3306 on your computer gets forwarded straight into the container’s port 3306.

Terminal window
docker run -d -p 3306:3306 --name my_mysql_container -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
# This runs the MySQL container in the background with a root password and the name "my_mysql_container".
# The -p flag connects port 3306 on your computer to port 3306 inside the container,
# so you can reach MySQL from outside the container.

A Docker image is not one single block of data. It is built from several layers stacked on top of each other. Each layer stores only the changes or new files added at that step. Because of this, Docker doesn’t need to save the same data twice, which saves a lot of storage space.

  • Each layer is “read-only,” meaning it never changes once it is created.
  • When you run a container, Docker adds one more layer on top that you can write to. This is where any changes you make while the container is running are stored.
  • Docker remembers (caches) layers. So if you build a new image that starts with the same steps as an older one, Docker reuses the old layers instead of rebuilding them. This makes builds much faster.
  • To see all the layers inside an image, run:
Terminal window
docker history <image>

This shows you the image’s history: every layer, along with its size.

Let’s say you have two Dockerfiles:

  1. A “MySQL latest” Dockerfile
  2. A “MySQL 8.0” Dockerfile

Both of these share some of the same layers, like the base image and some common setup steps. When Docker builds these two images, it only creates new layers for the parts that are actually different between them. So if you only change a few lines in one Dockerfile, Docker reuses the layers that are the same in both, making the build faster and using less storage.

graph TD A["Base Layer - e.g. ubuntu or alpine"] --> B["Intermediate Layer - installs & setup"] B --> C["Intermediate Layer - more config"] C --> D["Final Layer - your app code"] D --> E["Writable Container Layer - created only when you run the container"]
  • Base Layer: The very first layer of an image, usually a small operating system or runtime, like ubuntu or alpine.
  • Intermediate Layers: The layers added on top of the base layer, such as installed software or settings. These can be shared and reused by other images too.
  • Final Layer (Container Layer): The last layer, holding your actual app code and final setup. This is the layer that actually runs when you start a container.

When something goes wrong, these commands help you figure out what happened.

1. docker logs <container> - Shows you the output (logs) from a container, whether it’s running or stopped.

Terminal window
docker logs <container id or name>

2. docker exec -it <container> <command> - Runs a command inside a container that is already running.

Terminal window
docker exec -it <container id or name> <command>
# Example: open a bash shell inside a running container
docker exec -it <container id or name> bash
# If the container doesn't have bash, try sh instead
docker exec -it <container id or name> sh
# Type "exit" or press Ctrl + D to leave the shell.

3. docker inspect <container> - Gives you detailed information about a container, like its settings and current state.

Terminal window
docker inspect <container id or name>
# Shows details like network settings, environment variables, and more.

4. docker network ls - Lists all the Docker networks on your computer.

Terminal window
docker network ls
# Shows every network, including the default "bridge" network.

5. docker network inspect <network> - Gives detailed information about one specific network.

Terminal window
docker network inspect <network name or id>
# Shows which containers are connected and their IP addresses.

6. docker volume ls - Lists all the Docker volumes you have.

Terminal window
docker volume ls
# Shows every volume created on your computer.

7. docker volume inspect <volume> - Gives detailed information about one volume.

Terminal window
docker volume inspect <volume name or id>
# Shows where the volume is stored and how it's being used.

8. docker system df - Shows how much disk space your images, containers, and volumes are using.

Terminal window
docker system df
# Helps you see which images or containers are taking up the most space.

9. docker system prune - Deletes things you’re not using anymore (stopped containers, unused images, unused networks).

Terminal window
docker system prune
# Cleans up unused Docker resources.
# This helps free up disk space by removing things you no longer need.

10. docker stats - Shows live, real-time usage numbers for your running containers.

Terminal window
docker stats
# Shows CPU, memory, and network usage in real time.
# Helps you spot containers that are using too many resources.

Docker containers and Virtual Machines (VMs) both let you run apps in their own separate space, but they work in very different ways.

graph LR subgraph VM["Virtual Machine"] H1["Hardware"] --> HY["Hypervisor"] HY --> G1["Guest OS 1"] --> A1["App"] HY --> G2["Guest OS 2"] --> A2["App"] end subgraph DK["Docker"] H2["Hardware"] --> OS["Host OS"] OS --> E["Docker Engine"] E --> C1["Container 1 - App"] E --> C2["Container 2 - App"] end
  • Isolation: Containers share the host computer’s OS kernel but each runs in its own space, so many containers can run on one computer without clashing.
  • Lightweight: Containers start fast because they don’t need to boot a whole operating system.
  • Portability: A container packs the app with everything it needs, so it behaves the same in development, testing, and production.
  • Scalability: You can quickly add more containers when you need more power, and remove them when you don’t.
  • Best for: Microservices, CI/CD pipelines, and apps that need to scale up or down quickly.

Docker comes with built-in networking, so containers can talk to each other and to your computer. Docker networks let you create separate, isolated spaces for your containers, which makes things easier to manage and keep secure.

Docker networking also lets containers on different computers (hosts) talk to each other, which is useful for apps split across many services (microservices).

The default network type. It lets containers on the same computer talk to each other, while still keeping them separate from your actual computer.

1. docker network create <network_name> - Makes a new Docker network.

Terminal window
docker network create my_bridge_network
# Creates a new bridge network called "my_bridge_network".

2. docker network ls - Lists every Docker network.

Terminal window
docker network ls
# Lists all networks on your computer.

3. docker network inspect <network_name> - Shows details about one network.

Terminal window
docker network inspect my_bridge_network
# Shows which containers are connected and their IP addresses.

4. docker network connect <network_name> <container> - Connects a container to a network.

Terminal window
docker network connect my_bridge_network my_container
# Connects "my_container" to the "my_bridge_network" network.
# or connect it right when you create the container
docker run -d --name my_container --network my_bridge_network my_image

5. docker network disconnect <network_name> <container> - Removes a container from a network.

Terminal window
docker network disconnect my_bridge_network my_container
# Disconnects "my_container" from "my_bridge_network".

6. docker network rm <network_name> - Deletes a network.

Terminal window
docker network rm my_bridge_network
# Removes the network "my_bridge_network".
# Note: You can't remove a network while containers are still connected to it.
# Disconnect or remove those containers first.

7. docker network prune - Removes every network that isn’t being used.

Terminal window
docker network prune
# Deletes all unused Docker networks.

8. Using a host network:

Terminal window
docker run -d --name my_container --network host my_image
# Runs a container using the host network, so it can use your computer's network directly.
# Note: The container won't get its own IP - it uses your computer's IP instead.

9. Using an overlay network:

Terminal window
docker run -d --name my_container --network overlay my_image
# Runs a container using an overlay network so it can talk to containers on other computers.
# Note: You need a Docker Swarm or Kubernetes setup for overlay networks to work.

10. Using a macvlan network:

Terminal window
docker run -d --name my_container --network macvlan --mac-address 02:42:ac:11:00:02 my_image
# Runs a container with its own MAC address, so it shows up as a separate device on the network.

11. Using a “none” network:

Terminal window
docker run -d --name my_container --network none my_image
# Runs a container with networking completely turned off.
# Note: The container will not be able to connect to anything.

Let’s practice by setting up MongoDB and Mongo Express (a web tool for viewing MongoDB data) in containers.

  1. Create a Docker network first, so both containers can talk to each other.

    Terminal window
    docker network create mongo_network
    # Creates a new Docker network called "mongo_network".
  2. Understand the MongoDB container command:

    Terminal window
    docker run -d \
    --name mongo_container \
    --network mongo_network \
    -e MONGO_INITDB_ROOT_USERNAME=admin \
    -e MONGO_INITDB_ROOT_PASSWORD=secret \
    -p 27017:27017 \
    mongo:latest
    • -d: Run in the background.
    • --name mongo_container: Give the container an easy-to-remember name.
    • --network mongo_network: Connect it to the mongo_network we made earlier, so it can talk to other containers on the same network.
    • -e MONGO_INITDB_ROOT_USERNAME=admin: Set the database’s root (admin) username.
    • -e MONGO_INITDB_ROOT_PASSWORD=secret: Set the database’s root password.
    • -p 27017:27017: Connect port 27017 on your computer to port 27017 inside the container, so you can reach MongoDB from outside.
    • mongo:latest: Use the newest version of the official MongoDB image.
  3. Understand the Mongo Express container command:

    Terminal window
    docker run -d \
    --name mongo_express_container \
    --network mongo_network \
    -e ME_CONFIG_MONGODB_SERVER=mongo_container \
    -e ME_CONFIG_MONGODB_ADMINUSERNAME=admin \
    -e ME_CONFIG_MONGODB_ADMINPASSWORD=secret \
    -e ME_CONFIG_MONGODB_URL="mongodb://admin:secret@mongo_container:27017" \
    -p 8081:8081 \
    mongo-express:latest
    • -d: Run in the background.
    • --name mongo_express_container: Give it an easy name.
    • --network mongo_network: Connect it to mongo_network, so it can reach the MongoDB container.
    • -e ME_CONFIG_MONGODB_SERVER=mongo_container: Tell Mongo Express which MongoDB container to talk to (by its container name).
    • -e ME_CONFIG_MONGODB_ADMINUSERNAME=admin: The MongoDB admin username Mongo Express should log in with.
    • -e ME_CONFIG_MONGODB_ADMINPASSWORD=secret: The MongoDB admin password Mongo Express should log in with.
    • -e ME_CONFIG_MONGODB_URL="mongodb://admin:secret@mongo_container:27017": The full connection address. Here, admin is the username, secret is the password, mongo_container is the name of the MongoDB container, and 27017 is the default MongoDB port.
    • -p 8081:8081: Connect port 8081 on your computer to port 8081 in the container, so you can open Mongo Express in a browser.
    • mongo-express:latest: Use the newest version of the Mongo Express image.
  4. Start the MongoDB container:

    Terminal window
    docker run -d \
    --name mongo_container \
    --network mongo_network \
    -e MONGO_INITDB_ROOT_USERNAME=admin \
    -e MONGO_INITDB_ROOT_PASSWORD=secret \
    -p 27017:27017 \
    mongo:latest
  5. Start the Mongo Express container:

    Terminal window
    docker run -d \
    --name mongo_express_container \
    --network mongo_network \
    -e ME_CONFIG_MONGODB_SERVER=mongo_container \
    -e ME_CONFIG_MONGODB_ADMINUSERNAME=admin \
    -e ME_CONFIG_MONGODB_ADMINPASSWORD=secret \
    -e ME_CONFIG_MONGODB_URL="mongodb://admin:secret@mongo_container:27017" \
    -p 8081:8081 \
    mongo-express:latest
  6. Open your browser and go to http://localhost:8081 to see Mongo Express.

Docker Compose is a tool that lets you set up and run apps that need more than one container, all at once. Instead of typing long docker run commands one by one, you write everything into a single YAML file, which makes it much easier to manage and share your setup.

  • Multiple containers together: Define several services (containers) in one file and manage them all as a single app.
  • Service order: Say which services depend on others, so they start in the right order.
  • Environment variables: Set values to configure each service, so it’s easy to adjust settings for development, testing, or production.
  • Automatic networking: Docker Compose automatically creates a network for your services, so they can talk to each other without extra setup.

Docker Compose makes it much simpler to manage apps with many containers. You describe everything you want in one file, and then you can start, stop, or scale your whole app with a single command. It’s great for development and testing, and it also works well in production for managing bigger apps.

You write all your setup in one .yaml file, then run docker compose up to start every service listed in that file.

  1. Create a file named docker-compose.yml in your project folder.

  2. Write your services inside it, like this:

    version: '3.8' # States which Compose file format you're using. Newer versions of Docker Compose don't require this line, but it's still good practice to add it.
    services: # List the services (containers) in your app
    mongo: # Name of the service - you can call it anything you like
    image: mongo:latest # Use the newest MongoDB image
    container_name: mongo_container # The container's name
    environment: # Environment variables for the MongoDB container
    MONGO_INITDB_ROOT_USERNAME: admin
    MONGO_INITDB_ROOT_PASSWORD: secret
    ports: # Connect a computer port to a container port
    - "27017:27017"
    networks: # Which network to join
    - mongo_network
    mongo-express: # Name of the service
    image: mongo-express:latest # Use the newest Mongo Express image
    restart: always # Keep retrying to start this service if it fails,
    # this is useful because mongo-express depends on mongo being ready,
    # and this helps it recover if it starts too early. (For proper start order, use "depends_on" below.)
    depends_on:
    - mongo # Wait for the "mongo" service to start first
    container_name: mongo_express_container # The container's name
    environment: # Environment variables for Mongo Express
    ME_CONFIG_MONGODB_SERVER: mongo_container
    ME_CONFIG_MONGODB_ADMINUSERNAME: admin
    ME_CONFIG_MONGODB_ADMINPASSWORD: secret
    ME_CONFIG_MONGODB_URL: "mongodb://admin:secret@mongo_container:27017"
    ports:
    - "8081:8081"
    networks:
    - mongo_network
    # You don't have to write the network section yourself - Compose creates one automatically
    # when you run "docker compose up". But it's still good practice to define it clearly:
    networks: # Define the networks used by your services
    mongo_network: # Name of the network
    driver: bridge # Use the bridge network type

Modern Docker installs Compose as a plugin, so the command is docker compose (with a space). Older setups use docker-compose (with a hyphen) - both work the same way, so use whichever your system has.

1. docker compose up - Starts every service listed in docker-compose.yml.

Terminal window
docker compose -f <file_name> up <mode>
# Example: docker compose -f docker-compose.yml up -d
# The -d flag runs everything in the background.
# Without -d, you'll see all the logs printed live in your terminal.

2. docker compose down - Stops and removes every service listed in the file.

Terminal window
docker compose -f <file_name> down
# Example: docker compose -f docker-compose.yml down
# Removes all containers, networks, and volumes created by this file.
# Useful for cleaning everything up when you're done.
# To also delete the volumes, add the -v flag:
docker compose -f <file_name> down -v
# Example: docker compose -f docker-compose.yml down -v
# This removes the volumes too, along with everything else.

3. docker compose ps - Lists the services from the file and their status.

Terminal window
docker compose -f <file_name> ps
# Example: docker compose -f docker-compose.yml ps

4. docker compose stop - Stops the services without deleting them.

Terminal window
docker compose -f <file_name> stop
# Example: docker compose -f docker-compose.yml stop

5. docker compose start - Starts services that were stopped.

Terminal window
docker compose -f <file_name> start
# Example: docker compose -f docker-compose.yml start

6. docker compose restart - Restarts the running services.

Terminal window
docker compose -f <file_name> restart
# Example: docker compose -f docker-compose.yml restart

7. docker compose logs - Shows logs from every service.

Terminal window
docker compose -f <file_name> logs
# Example: docker compose -f docker-compose.yml logs
# To see logs for just one service, add its name:
docker compose -f <file_name> logs <service_name>
# Example: docker compose -f docker-compose.yml logs mongo
# Shows only the logs for the "mongo" service.

8. docker compose exec <service> <command> - Runs a command inside a running service.

Terminal window
docker compose -f <file_name> exec <service> <command>
# Example: docker compose -f docker-compose.yml exec mongo bash
# Opens a bash shell inside the "mongo" service's container.
# Swap "bash" for any command you need.
# Example - running a MongoDB shell command inside the container:
docker compose -f <file_name> exec mongo mongosh -u admin -p secret --authenticationDatabase admin
# Connects to the MongoDB instance using the given username and password.
# If bash isn't available, try sh:
docker compose -f <file_name> exec mongo sh

9. docker compose build - Builds (or rebuilds) the services in the file.

Terminal window
docker compose -f <file_name> build
# Example: docker compose -f docker-compose.yml build
# Useful after you've changed your Dockerfile or app code.
# You can also build just one service:
docker compose -f <file_name> build <service_name>
# Example: docker compose -f docker-compose.yml build mongo

10. docker compose config - Checks and displays the final, resolved settings from your file.

Terminal window
docker compose -f <file_name> config
# Example: docker compose -f docker-compose.yml config
# Useful for checking the file is written correctly before running it.
# It shows the final settings, including environment variables and default values.
# To list just the service names:
docker compose -f <file_name> config --services
# Example: docker compose -f docker-compose.yml config --services

“Dockerizing” an app means putting it, along with everything it needs, into a Docker image, so it can run the same way anywhere.

  • Portability: A Docker image runs the same way on any computer that has Docker.
  • Isolation: Every container runs alone, so it won’t clash with other apps or their dependencies.
  • Scalability: You can add or remove containers any time you need more or less power.
  • Consistency: Your app behaves the same in development, testing, and production, so you avoid the classic “it works on my machine” problem.
  1. Create a Dockerfile: This file holds the instructions for building your image - things like the base image, your app’s code, its dependencies, and any setup steps.

  2. Build the image: Use docker build to turn your Dockerfile into an actual Docker image.

  3. Run the container: Use docker run to start a container from that image, so your app runs in its own isolated space.

  4. Test it: Make sure your app works correctly inside the container. You can use docker exec to run test commands inside a running container.

  5. Share it: If you want others to use your image, push it to a registry like Docker Hub.

  1. FROM - Says which base image to start from. Usually an official image from Docker Hub.
  2. WORKDIR - Sets the working folder inside the container for the commands that come after it (RUN, CMD, COPY, and so on).
  3. COPY - Copies files from your computer into the image.
  4. RUN - Runs a command while the image is being built.
  5. CMD - Sets the default command that runs when a container starts from this image.
  6. EXPOSE - Tells Docker which network port the container will use.
  7. ENV - Sets environment variables inside the image.
# Use the official Node.js image as our starting point
FROM node:14
# Set the working folder inside the container
WORKDIR /app
# Copy package.json and package-lock.json first
COPY package*.json ./
# Install the app's dependencies
RUN npm install
# Copy the rest of the app's code
COPY . .
# Let Docker know the app uses this port
EXPOSE 3000
# Set environment variables for connecting to MongoDB
ENV MONGO_DB_USERNAME=admin \
MONGO_DB_PASSWORD=secret \
MONGO_DB_HOST=localhost \
MONGO_DB_PORT=27017 \
MONGO_DB_DATABASE=mydb
# The command that runs when the container starts
CMD ["npm", "start"]
  • FROM node:14: Uses the official Node.js version 14 image as the base for our app.
  • WORKDIR /app: Sets /app as the working folder inside the container. Every command after this runs from there.
  • COPY package*.json ./: Copies package.json and package-lock.json into the working folder.
  • RUN npm install: Installs the dependencies listed in package.json.
  • COPY . .: Copies the rest of your app’s code into the working folder.
  • EXPOSE 3000: Tells Docker the app will use port 3000.
  • ENV ...: Sets environment variables the app can use to connect to MongoDB.
  • CMD ["npm", "start"]: Sets the command that runs automatically when a container starts from this image.

Go to the folder that has your Dockerfile, and run:

Terminal window
docker build -t my-node-app:1.0 .
# Builds a Docker image named "my-node-app" with the tag "1.0".
# The -t flag lets you set a name and version (tag) for your image.
# Swap "my-node-app" and "1.0" (or "latest") for whatever name and version you want.
Terminal window
docker run -p 3000:3000 my-node-app:1.0
# Runs a container from the "my-node-app:1.0" image, connecting port 3000 in the container to port 3000 on your computer.
# Swap "my-node-app:1.0" with your own image name and version.
# The -p flag maps the container's port to your computer's port,
# so you can open the app in your browser at http://localhost:3000.

Once your image is ready, you can share it with others by uploading it to Docker Hub, which is a free cloud registry for Docker images.

  1. Make a Docker Hub account if you don’t already have one.

  2. Create a repository on the Docker Hub website for the image you want to publish.

  3. Build your image using the same name as your Docker Hub repository. For example, if your repository is myusername/my-node-app, build it like this:

    Terminal window
    docker build -t myusername/my-node-app:1.0 .
    # Builds an image named "myusername/my-node-app" with tag "1.0".
    # Replace "myusername" with your actual Docker Hub username,
    # and "my-node-app" with your repository name.
  4. Log in to Docker Hub from your terminal:

    Terminal window
    docker login
    # Logs you into Docker Hub using your Docker Hub username and password.
  5. Push (upload) your image to Docker Hub:

    Terminal window
    docker push myusername/my-node-app:1.0
    # Uploads your image to Docker Hub.
    # Replace "myusername" and "my-node-app:1.0" with your own details.
  6. Go to your Docker Hub repository page in a browser to check that your image was uploaded successfully.

FROM python:3-alpine3.15
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 3000
CMD ["python", "./index.py"]

To build it:

Terminal window
docker build -t krsahil8825/hey-python-flask:0.0.1.RELEASE .

-t krsahil8825/hey-python-flask:0.0.1.RELEASE sets the name and tag (version) for this image. The . at the end tells Docker to look for the Dockerfile in the current folder - you can also give a different path there if you need to.

Project folder structure for this example:

folder/
|___Dockerfile
|___index.py
|___requirements.txt

Docker volumes are how you keep (persist) data even after a container stops or gets deleted. Instead of storing data only inside the container, volumes store it outside, on your computer, so it doesn’t disappear and can even be shared between different containers.

  1. Data doesn’t disappear: Your data stays safe even if the container is stopped or removed. This matters a lot for databases and anything that needs to remember its state.
  2. Sharing data: The same volume can be used by more than one container, making it easy to share data or config files.
  3. Better speed: Volumes can be faster than the container’s own filesystem, especially for apps that read and write files a lot.
  4. Backup and restore: It’s easy to back up and restore a volume, which makes managing your data much simpler.

Docker volumes let you store data from a container outside of the container. That way, your data survives even after the container stops or gets deleted.

There are two ways to use volumes:

  1. Use a folder on your own computer.
  2. Use a volume that Docker creates and manages for you (a named volume).

You can point Docker to a folder that already exists on your computer.

Step 1: Make a folder on your computer

Terminal window
mkdir ~/desktop/data

Step 2: Run the container and connect that folder

Terminal window
docker run -it -v /users/your_username/desktop/data:/data my_image

What this means:

  • -v /users/your_username/desktop/data:/data connects a folder on your computer (/users/your_username/desktop/data) to a folder inside the container (/data).
  • my_image is the name of the image you want to run.
  • -it opens an interactive terminal so you can type commands inside the container.

You can also let Docker create and manage a volume for you.

Step 1: Create the volume

Terminal window
docker volume create my_volume
  • This makes a volume named my_volume.
  • You don’t need to make a folder yourself - Docker takes care of it.

Step 2: Run a container using that volume

Terminal window
docker run -d --name my_container --volume my_volume:/data my_image

What this means:

  • -d: Runs the container in the background.
  • --name my_container: Gives the container a name.
  • --volume my_volume:/data: Connects the volume my_volume to the /data folder inside the container.

You can go inside the running container to look at the volume’s contents.

Terminal window
docker exec -it my_container /bin/bash
  • This opens a shell inside the container named my_container.

Then, inside the container, run:

Terminal window
cd /data
  • This takes you to the /data folder, which is where the volume is connected.
  • Any file you create or read there will stay safe, even if the container stops.
MethodYou Control the Folder?Data Stays Safe?Notes
Host folderYesYesUses a specific folder on your computer
Docker volumeNoYesDocker manages the storage for you
FeatureHost Folder (Bind Mount)Docker Volume
What it isUses a folder that already exists on your computerDocker creates and manages its own storage
SetupYou must create the folder yourselfDocker sets it up automatically
PortabilityTied to your specific computerWorks well across different environments
SecurityLess secure (it exposes a real path on your system)More secure (kept in a Docker-managed area)
Backup and sharingEasy to look at and back up manuallyNeeds docker cp or a volume mount to access
Speed (on Linux/WSL)Slower, especially on Windows/macOS through WSLFaster - Docker handles the file access itself
Good for production?Usually not recommendedYes, recommended for production
Good for development?Great for editing files liveGood too, if you don’t need to see the files directly
SituationUse This
You’re just learning or debuggingHost Folder (Bind Mount)
You want to edit files on your computer while the container is runningHost Folder
You’re building a real project, production app, or need better performanceDocker Volume
You don’t want to deal with file paths or permission issuesDocker Volume
version: '3.8'
services:
mongo:
image: mongo:latest
container_name: mongo_container
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
ports:
- "27017:27017"
volumes:
# Use only ONE of the following volume types:
# Option 1: Use a host bind mount (a folder on your computer)
- /users/your_username/desktop/data:/data/db
# Option 2: Use a Docker named volume
- my_volume:/data/db
mongo-express:
image: mongo-express:latest
container_name: mongo_express_container
environment:
ME_CONFIG_MONGODB_SERVER: mongo_container
ME_CONFIG_MONGODB_ADMINUSERNAME: admin
ME_CONFIG_MONGODB_ADMINPASSWORD: secret
ME_CONFIG_MONGODB_URL: "mongodb://admin:secret@mongo_container:27017"
ports:
- "8081:8081"
# Declare named volumes here (only needed if you use a named volume above)
volumes:
my_volume:
driver: local

1. List all volumes

Terminal window
docker volume ls
# Lists every Docker volume on your computer.

2. Inspect a volume (also called a “named volume”)

Terminal window
docker volume inspect my_volume
# Shows details about the volume, like where it's stored and how it's used.

3. Remove a volume

Terminal window
docker volume rm my_volume
# Deletes the volume you named.

4. Remove all unused volumes

Terminal window
docker volume prune
# Deletes every volume that isn't attached to a container.

5. Attach a volume to a running container

  • -v is short for --volume.
  • -m is short for --mount.
Terminal window
docker run -v VOL_NAME:CONT_DIR
# Example: docker run -v my_volume:/data/db my_image
# Connects the named volume to the given folder inside the container.
# You can also use --mount instead of -v:
docker run --mount type=volume,source=my_volume,target=/data/db my_image
# This does the same thing as above, using the --mount flag instead.

1. Host Bind Mounts: Lets you use a folder from your own computer as a volume inside a container. Great for development, since you can edit files on your computer and see the changes reflected in the container right away.

2. Docker Named Volumes: Managed by Docker and stored in a specific place on your computer. Great for keeping data safe between container runs, and can be shared between multiple containers. Easier to manage and move than bind mounts.

3. Anonymous Volumes: Created automatically when you use -v without giving it a name. Harder to find and manage directly, and usually used for short-term or temporary data. These get deleted automatically when their container is removed.

The .dockerignore file tells Docker which files and folders to skip when it builds an image. It works just like .gitignore, but for Docker builds instead of Git.

When you build an image, Docker sends all the files in your project folder to something called the build context (the Docker daemon that actually does the building). If you don’t skip unneeded files, this can:

  • Make your builds slower
  • Make your images bigger
  • Accidentally leak private data (like passwords or API keys)
  • Put it in the same folder as your Dockerfile.
  • It only affects the build that uses that folder.

Each line is a pattern telling Docker what to skip.

PatternWhat It Means
file.txtSkip file.txt in the main folder
folder/Skip the entire folder
*.logSkip every file ending in .log
**/temp/Skip any folder named temp, anywhere
!keep.txtDon’t skip this file (an exception)
# commentJust a comment line, ignored by Docker

Project structure:

my-app/
│-- Dockerfile
│-- .dockerignore
│-- node_modules/
│-- logs/
│-- src/
│ ├── main.js
│ └── secret.txt
│-- README.md

.dockerignore file:

# Skip node_modules (not needed in the image)
node_modules/
# Skip logs
logs/
*.log
# Skip secret files
secret.txt
# Keep README.md even though we skip all other .md files
*.md
!README.md

When you run:

Terminal window
docker build -t my-image .

Docker will not send node_modules/, logs/, secret.txt, or any .log files to the build.

Faster builds

Sending fewer files to Docker means the build starts faster.

Smaller images

Less unnecessary data means smaller, cleaner images.

Better security

Keeps secret files (like passwords or keys) out of the image entirely.

  1. Always skip big folders you don’t need in the container, like node_modules/, .git/, venv/, or build/.

  2. Skip development-only files, like .env, *.log, and test data.

  3. Use ! to keep specific important files, even while skipping others.

  4. Double-check before building - run:

    Terminal window
    docker build --no-cache -t test .

    then check what’s inside the image to make sure nothing unwanted got included.

Here’s a ready-to-use template you can start with:

# Git files
.git
.gitignore
# Python
__pycache__/
*.pyc
venv/
# Node.js
node_modules/
npm-debug.log
yarn-error.log
# Logs
*.log
logs/
# Build output
dist/
build/
# Environment files
.env
.env.local

Run:

Terminal window
docker build -t my-app .

Docker will print a line like:

Sending build context to Docker daemon 3.2MB

If that number looks too big, go back and update your .dockerignore file.