DevOps.Academy
CoursesCurriculumPricing

    Loading…

    ↑ ↓ move · ↵ open · Ctrl K anywhere

    ◔My learningSign in
    DevOps.Academy

    Free, hands-on DevOps lessons. Made with care in India.

    Learn

    All coursesLinux roadmapCurriculumMy learning

    Academy

    PricingCertificates

    Legal

    PrivacyTermsContact

    devops.rajeev.pro

    DockerLesson 4 of 14
    Your progress · 0%
    On this page
    1. Images and containers
    2. docker run
    3. Publishing ports
    4. Logs and exec
    5. Stop, start, remove
    ↖ Course roadmap
    Lesson 4 · Docker basics

    Docker basics: images, containers, ports, logs and exec

    Run your first real containers: pull an image, start a web server, publish its port, read its logs, open a shell inside, and stop, start and remove it, knowing what each step changes.

    30 min read · beginner · hands-on

    By the end of this lesson you'll be able to

    0 of 5 complete

    Images and containers

    An image is a read-only template: a filesystem and a default command. A container is one run of an image, with its own process, network and writable layer (lesson 2). One image, many containers:

    $ docker pull nginx:1.30-alpine
    $ docker images
    REPOSITORY   TAG           IMAGE ID       SIZE
    nginx        1.30-alpine   970364228b1e   102MB
    
    docker pull nginx:1.30-alpine
    • dockerbuild and run containers
    • pulldownload an image
    • nginx:1.30-alpinethe image
    docker images
    • imageslist images

    The tag (1.30-alpine) picks a version and variant. nginx releases stable versions with an even minor number (1.30) and mainline with odd (1.31); -alpine is the small Alpine Linux base. Pin a tag in anything you'll run twice: nginx alone means nginx:latest, which changes under you.

    docker run

    $ docker run -d --name web -p 8080:80 nginx:1.30-alpine
    3893cb7f5c00f1dfad90d75368565eaee2507636450773077c73bb9f8e20cfb9
    $ docker ps
    CONTAINER ID   IMAGE               STATUS          PORTS                                     NAMES
    3893cb7f5c00   nginx:1.30-alpine   Up 13 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   web
    
    docker run -d --name web -p 8080:80 nginx:1.30-alpine
    • dockerbuild and run containers
    • runstart a new container
    • -drun in the background
    • --name webname the container
    • -p 8080:80publish a port, host:container
    • nginx:1.30-alpinethe image to start
    docker ps
    • pslist running containers
    FlagDoes
    -drun in the background and print the container ID
    --name weba name to use instead of the ID
    -p 8080:80publish container port 80 on host port 8080
    -itinteractive with a terminal, for shells: docker run -it alpine:3.22 sh
    --rmdelete the container when it exits: for one-off commands
    -e KEY=valueset an environment variable inside

    Open http://localhost:8080 and nginx answers from inside the container:

    The page served by the container, through host port 8080.© Screenshot: DevOps Academy

    The page served by the container, through host port 8080.© Screenshot: DevOps Academy

    Publishing ports

    Each container has its own network namespace, so port 80 inside isn't port 80 on your machine. -p connects them: host port first, container port second.

    $ curl -sI localhost:8080 | head -2
    HTTP/1.1 200 OK
    Server: nginx/1.30.6
    $ docker run -d --name web2 -p 8081:80 nginx:1.30-alpine   # a second nginx: also port 80 inside
    
    curl -sI localhost:8080 | head -2
    • curlmake an HTTP request
    • -ssilent: no progress bar
    • -Iheaders only
    • |pipe: send output to the next command
    • headshow the first lines
    docker run -d --name web2 -p 8081:80 nginx:1.30-alpine
    • dockerbuild and run containers
    • runstart a new container
    • -drun in the background
    • --name web2name the container
    • -p 8081:80publish a port, host:container
    • nginx:1.30-alpinethe image to start

    Both containers listen on port 80 in their own namespace; on the host they're 8080 and 8081. Two containers can't publish the same host port: the second fails with failed to bind host port 0.0.0.0:8080/tcp: address already in use.

    0.0.0.0:8080 in docker ps means "every address of the host", so other machines can reach it. Bind to the loopback only when that's what you want: -p 127.0.0.1:8080:80. Lesson 10 explains container networking fully.

    Logs and exec

    $ docker logs web | tail -2
    172.17.0.1 - - [16/Oct/2026:05:17:05 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.18.0" "-"
    172.17.0.1 - - [16/Oct/2026:05:17:05 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.18.0" "-"
    $ docker logs -f --since 5m web       # follow new lines, from the last 5 minutes
    $ docker exec web nginx -v            # run a command inside
    nginx version: nginx/1.30.6
    $ docker exec -it web sh              # a shell inside; exit to leave (the container keeps running)
    / # ls /usr/share/nginx/html
    50x.html    index.html
    
    docker logs web | tail -2
    • dockerbuild and run containers
    • logsshow a container’s output
    • webthe container
    • |pipe: send output to the next command
    • tailshow the last lines
    docker logs -f --since 5m web
    • -fkeep following new output
    • --since 5monly output newer than this
    docker exec web nginx -v
    • execrun a command in a running container
    • nginx -vthe command to run inside it
    docker exec -it web sh
    • -itinteractive, with a terminal
    • shthe command to run inside it

    docker logs shows what the main process writes to stdout and stderr. The requests come from 172.17.0.1: the default bridge network's gateway, which is how your curl reached the container.

    Stop, start, remove

    stop ends the process but keeps the container. rm deletes the container. Neither touches the image.© Animation: DevOps Academy

    stop ends the process but keeps the container. rm deletes the container. Neither touches the image.© Animation: DevOps Academy
    $ docker stop web                     # SIGTERM, then SIGKILL after 10 s
    $ docker ps -a --filter name=web --format 'table {{.Names}}\t{{.Status}}'
    NAMES     STATUS
    web       Exited (0) 3 seconds ago
    $ docker start web                    # the same container, the same files
    $ docker rm -f web web2               # stop if needed, then delete
    $ docker rmi nginx:1.30-alpine        # delete the image (only if no container uses it)
    
    docker stop web
    • dockerbuild and run containers
    • stopstop a container (SIGTERM, then SIGKILL)
    • webthe container
    docker ps -a --filter name=web --format 'table {{.Names}}\t{{.Status}}'
    • pslist running containers
    • -ainclude stopped ones too
    • --filter name=webshow only matching items
    • --format 'table {{.Names}}\t{{.Status}}'print only these fields (Go template)
    docker start web
    • startstart a stopped container
    docker rm -f web web2
    • rmdelete a container
    • -fstop it first if running
    • web2the container
    docker rmi nginx:1.30-alpine
    • rmidelete an image
    • nginx:1.30-alpinethe image
    CommandThe processThe container and its filesThe image
    docker stopendedkeptkept
    docker startstarted againsame oneskept
    docker rm(must be stopped, or -f)deletedkept
    docker rmideleted
    Name what you keep, --rm what you don't

    Give long-running containers a --name; add --rm to one-off commands (docker run --rm alpine:3.22 date) so stopped containers don't pile up in docker ps -a. Lesson 12 shows how to clean up the rest.

    ⌘
    Mini mission

    Serve your own page from nginx

    Run nginx in the background on host port 8080, replace its welcome page with your own using docker exec, prove it with curl, read the request in the logs, then stop, start and remove the container, checking what survives each step.

    Input: DockerOutput: curl localhost:8080 showing your page, then a clean docker ps -a
    Reveal one safe solution
    $ docker run -d --name web -p 8080:80 nginx:1.30-alpine
    $ docker exec web sh -c 'echo "<h1>Hello from my container</h1>" > /usr/share/nginx/html/index.html'
    $ curl -s localhost:8080
    <h1>Hello from my container</h1>
    $ docker logs web | tail -1                      # your curl, logged
    $ docker stop web && docker start web && curl -s localhost:8080
    <h1>Hello from my container</h1>
    $ docker rm -f web
    $ docker run -d --name web -p 8080:80 nginx:1.30-alpine && sleep 1 && curl -s localhost:8080 | grep -o '<title>.*</title>'
    <title>Welcome to nginx!</title>
    $ docker rm -f web
    
    docker run -d --name web -p 8080:80 nginx:1.30-alpine
    • dockerbuild and run containers
    • runstart a new container
    • -drun in the background
    • --name webname the container
    • -p 8080:80publish a port, host:container
    • nginx:1.30-alpinethe image to start
    docker exec web sh -c 'echo "<h1>Hello from my container</h1>" > /usr/share/nginx/html/index.html'
    • execrun a command in a running container
    • webthe container
    • sh -c 'echo "<h1>Hello from my container</h1>" > /usr/share/nginx/html/index.html'the command to run inside it
    curl -s localhost:8080
    • curlmake an HTTP request
    • -ssilent: no progress bar
    docker logs web | tail -1
    • logsshow a container’s output
    • |pipe: send output to the next command
    • tailshow the last lines
    docker stop web && docker start web && curl -s localhost:8080
    • stopstop a container (SIGTERM, then SIGKILL)
    • &&run the next command only if this one worked
    • startstart a stopped container
    docker rm -f web
    • rmdelete a container
    • -fstop it first if running
    docker run -d --name web -p 8080:80 nginx:1.30-alpine && sleep 1 && curl -s localhost:8080 | grep -o '<title>.*</title>'
    • grepfind lines that match a pattern
    • -oprint only the matching part

    Your page survives stop and start (same container, same writable layer), but not rm: the new container starts from the untouched image, with nginx's default page. Lesson 6 shows how to keep data across containers.

    Try it yourself

    0 of 6 steps done

    Run each task in your own account or terminal, then tick it off.

    Knowledge check

    Quick check

    Question 1 of 5

    What is the difference between an image and a container?

    Go deeper

    Official reading for this lesson

    • DocsDocker Docs: docker container runDocker Docs ↗
    • DocsDocker Docs: publishing and exposing portsDocker Docs ↗
    • DocsDocker Docs: docker container logsDocker Docs ↗
    • Toolnginx official image on Docker HubDocker Hub ↗

    Or get every quiz answer right and it completes itself.

    ← PreviousInstalling Docker: Desktop, Engine on Linux, and rootlessNext lesson →Other people's images: databases, tools and runtimes