Aflorzy logo

Getting Started with Docker

Published Nov 4, 2024

Updated Jun 12, 2025

Spin up your first containers using Docker Run and Docker Compose!

Written by Andrew Flores

dockertutorial

Welcome to the world of Docker and containerization! Containers are incredibly useful for making applications portable so they can easily be started up on any computer and behave the exact same way on each. Docker is far and away the most widespread containerization platform, although there are alternatives such as Podman that are gaining popularity in recent years.

If you haven’t installed Docker on your system yet, go check out my Docker Installation Guide before continuing here.

There are two primary ways you can spin up Docker containers. Each has its own benefits and I will elaborate more below.

Quick Start

The first container Docker’s documentation suggests running is “hello-world”. This is typically run to ensure that Docker commands can be run in your terminal, that images can be pulled from Docker’s own container registry (hub.docker.com), and that there are no unforeseen errors along the way.

Terminal window
docker run hello-world

Run this command in your terminal to see if hello-world works for you.

This is the essentially the simplest container that can be run, and doesn’t serve much utility aside from a smoke test. Most containers that you will run are going to require a few more parameters to start properly, so the rest of this article will cover how to interpret some of the cases you will encounter.

Portainer

Portainer is always the first container I spin up after installing Docker, and I will use it in all of the following examples in this article due to its relative simplicity and immensely useful capabilities.

Portainer is a Docker management tool that provides an intuitive web interface for interacting with your containers. I use it almost exclusively to start new containers and edit existing ones.

There is a feature called “Stacks” that is equivalent to using Docker Compose. You can write your compose YAML in the text box, and include any secrets in the environment variables section.

See their official Docker docs here.

Docker Run

docker run commands are a quick and convenient way to spin up individual containers by copy-pasting a single command into your terminal. Many Docker examples online will provide you with a docker run command that you can use as a “one click” solution to running the container on your own machine.

Although these commands can be convenient, I tend to avoid using them to spin up containers in favor of Docker Compose that provides other benefits that better suit my needs.

Terminal window
docker run -d \
-p 9000:9000 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ./data:/data \
portainer/portainer-ce:latest

Note: This is a single command but is written to appear multi-line using \ at the end of each component for ease of readability. Feel free to type the command out in one line without those characters and it will behave the same.

Docker Compose

Docker Compose is a file-based approach to initializing containers, and has a couple major benefits over the docker run command.

  1. Container configuration is stored in a file that can be easily edited and checked into version control systems in order to keep files backed up and stored in a single place.
  2. Multiple containers can be added to a single Compose file and all of them can easily be spun up or down simultaneously. This is very helpful when your application requires multiple full-stack technologies such as a frontend, backend, and database. Each can be defined in its own container, with all of the configurations residing inside of a single docker-compose.yml file.
version: 3
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
ports:
- 9000:9000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
  1. Create a new folder called portainer and cd into it
  2. Paste these lines into a new file named docker-compose.yml
  3. Run docker-compose up -d or docker compose up -d depending on the version of Docker you have installed.
  4. Verify that Portainer is now running in the background by executing docker ps and finding portainer in the list.
  5. Navigate to the web interface in your browser by visiting http://localhost:9000

Breaking It Down

Referencing the Docker Compose YAML from above, we’re going to go line-by-line to understand the purpose of each.

version: 3

Specifying version actually appears to be obsolete according to the docs, but all examples of Docker Compose configs that I see online include it. The version number also doesn’t seem to have much effect from my experience, and specifying 3 or 3.8 (with or without surrounding quotation marks) should work in most cases.


services:
portainer:

Specifies a list of services in the Docker Compose file. Individual service names are listed with one indent before them. All service configurations are defined with 2+ indents, per YAML object notation. For example:

services:
service1:
image: image-1
service2:
image: image-2
serviceN:
image: image-n

image: portainer/portainer-ce:latest

Defines the Portainer image to pull down from Docker Hub and instantiate the container with. See the “Tags” tab at that link for variations of the portainer/portainer-ce image. I use the latest tag which allows the container to use the most recent release. Note that an updated image will not be pulled to your machine automatically while the container is running.


container_name: portainer

Provides a friendly name for the container so you can easily find it in a list, such as the one produced by the docker ps command.


restart: always

Defines the restart policy for the container. The restart policy comes into play whenever a container is stopped for any reason. Reasons for stopping include an internal container error, a restart of the host computer running Docker, or a restart of the Docker service itself. The possible options are as follows:


ports:
- 9000:9000

Defines the ports to expose on your host machine. Services are usually running inside of the containers, and the service often times exposes a web interface on a port that you can visit in your web browser (e.g. http://localhost:9000). Containers allow you to map the internal container port to a port on your host machine. You are allowed to change the port that is used on your computer, but not the internal port as that was defined by the author of the container image and the image would need to be rebuilt if a change was to be made there.

ports:
- <port on host machine>:<port inside container>
- 9000:9000
- 81:80

Some containers have internal services running on multiple ports, and you can map each of them to ports on your host machine by defining a list. The right-hand side of the colon (:) is the internal container port, and cannot be changed. Docker syntax always follows this pattern of host:container, even for non- port-related configuration.


volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data

Volumes provide a way for containers to persist data. Volumes can either be bind mounts or Docker Volumes.

Docker Volumes:

Bind Mounts:

services:
portainer:
volumes:
- <path on host machine>:<path inside container>
- ./data:/data # 1
- portainer-data:/data # 2
volumes:
portainer-data: # 3
  1. Maps a folder named data in the current directory (same folder this docker-compose.yml is located in) to the directory /data inside the container. The internal path is typically defined by the container author and is found in the documentation for running the container.
  2. Maps a Docker Volume named portainer-data to the /data directory inside the container. This volume is defined in the same Compose file at the bottom (# 3).

Final Thoughts

Now that you have your first useful container up and running, go find some more! How about setting up a dashboard to easily view all your services? Homepage is the dashboard container I have been running for a while and it works great! Otherwise, some quick Google searches for “Homelab Services” will yield plenty of suggestions from fellow homelabbers.

One last piece of advice: try to avoid copying “all-in-one” Docker Compose files that you find online. There most likely isn’t anything malicious about them, but my experience with them has led to many headaches and hours of troubleshooting. I have found it is best to start with the minimum viable configuration for each service, and ensure that all of the containers are able to successfully spin up and that their web interface can be accessed. Then, start adding in some extra parameters that were in the online example such as environment variables or volume mounts, and check that the containers can still spin up as expected. It is much easier to diagnose where problems arose when you configured the containers incrementally.