零基础学Docker:10个视频教程+避坑实战
发布日期: 2026/08/01 阅读总量: 0

先说我那次凌晨两点的惨剧

2020年11月,我在一台CentOS 7.6上给公司部署一个PHP 7.2老项目。手动装Nginx、PHP、MySQL、Redis,装了四个小时,最后一步PHP连不上MySQL,报错 SQLSTATE[HY000] [2002] Connection refused。查了半小时,发现MySQL的socket路径和PHP编译时的默认路径不一致。当时我就在工位上骂街,然后凌晨两点翻出落灰的Docker学习笔记。

后来我用三天看完10个视频教程,第四天把那个老项目容器化部署成功。从手动部署4小时+排错2小时,变成一条命令拉起全部环境。这篇文章记录的就是我当时看完觉得真正有用的10个视频,以及每一阶段对应的代码练习和坑。

问题:视频教程几百个,真正能让你动手的没几个

先交代一下背景。我当时的情况是:Linux命令会一点,从来没写过Dockerfile,对「镜像」「容器」的理解停留在「这玩意和虚拟机差不多」的阶段。

然后我去B站搜Docker教程。搜索结果前五页,有800多集的机构全套课,有30分钟速成,有各种「面试必问」。

我踩了三次无效学习的坑:

  • 跟着8小时大课敲命令,敲完就忘,因为老师把每条命令都讲了,但我不知道什么场景下该用哪条。
  • 看「Docker快速入门15分钟」,学了一堆名词,打开终端还是不会写Dockerfile。
  • 看生产环境部署视频,人家用Kubernetes,我当时只需要单机跑三个容器,杀鸡用牛刀。 最终我总结出三个筛选标准:第一,视频必须带着你从头写一个Dockerfile,而不是只演示 docker run;第二,必须有docker-compose内容,因为实践项目几乎不会单容器;第三,时长在30分钟到3小时之间,太长看不完,太短说不透。

    方案对比:自学Docker的4条路径

    除了看视频,我还试过别的学习路径,直接说结论。

    学习路径投入时间达到可部署项目的水平我的判断
    只看官方文档30+小时难,官方文档默认你懂容器网络原理不适合入门
    自己瞎试无限不保证遇到坑没人讲,容易劝退
    买一本书通读40+小时可以但是书里示例太老,多数停留在docker run
    按以下10个视频路线走约14小时(含练习)可以独立部署LNMP项目、写Compose见效最快

    这10个视频不是一次性推荐的,我拆成了三个学习阶段,每个阶段配了必须动手敲的代码。

    第一阶段:跑起来(视频1-4)

    视频1:TechWorld with Nana - Docker Tutorial for Beginners(约2小时)

    B站搜「Docker Tutorial for Beginners Nana」,有搬运。这个视频解决了「Docker到底是什么」的问题。她不讲内核原理,用白板把Container和VM的区别画得很清楚。看完这个视频,我的理解变成一句话:镜像就是一层层只读文件叠加,容器就是上面加了一个可写层。

    看完做这个练习:安装Docker,然后跑通下面这组命令。

    # 安装docker(Ubuntu 22.04.3,Docker 24.0.7)
    sudo apt-get update
    sudo apt-get install -y ca-certificates curl gnupg
    sudo install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    sudo chmod a+r /etc/apt/keyrings/docker.gpg
    
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    
    sudo apt-get update
    sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    
    # 启动并验证
    sudo systemctl enable --now docker
    docker --version
    # 输出 Docker version 24.0.7, build afdd53b

    这里注意一点:Ubuntu的官方Docker源在国内速度很慢,等下避坑段我会给加速方案。

    视频2:狂神说Java - Docker最新超详细版教程(B站约3小时)

    这是中文里讲得最入门友好的,2019年发布但Docker命令基本没变。亮点是讲清楚了三个核心命令的关系:docker build构建镜像,docker run创建容器,docker compose编排多容器。

    看完做这个练习:用Nginx起一个网页,然后把容器的端口映射搞明白。

    # 1. 创建容器,映射宿主机8080到容器80
    docker run -d --name test-nginx -p 8080:80 nginx:1.25.3
    
    # 2. 写一个自己的主页
    mkdir -p ~/docker-demo/html
    echo '<h1>Docker is OK</h1>' > ~/docker-demo/html/index.html
    
    # 3. 把文件挂载进容器(不用重新build)
    docker run -d --name test-nginx2 \
      -p 8081:80 \
      -v ~/docker-demo/html:/usr/share/nginx/html:ro \
      nginx:1.25.3
    
    # 访问 http://localhost:8081 就能看到内容
    curl http://localhost:8081
    # 输出 <h1>Docker is OK</h1>

    做完这个练习,你能直观理解两件事:端口映射是把容器端口暴露到宿主机;挂载卷是把宿主机目录覆写到容器目录。

    视频3:尚硅谷Docker基础篇(B站,约4小时)

    这个视频内容多,但你必须看完前2小时,因为它是唯一一个把镜像分层机制讲透的中文课。老师用 docker history 命令一行行分析ubuntu镜像的每一层,看完你就明白为什么Docker Hub上有些镜像几百MB,有些才几十MB。

    看完做这个练习,动手构建自己的第一个镜像。

    # 文件路径: ~/docker-demo/Dockerfile
    # 用的是一个极简基础镜像
    FROM alpine:3.19
    
    # 安装curl(alpine的包管理是apk)
    RUN apk add --no-cache curl
    
    # 设置工作目录
    WORKDIR /app
    
    # 复制一个脚本进去
    COPY hello.sh .
    
    # 执行权限
    RUN chmod +x hello.sh
    
    CMD ["./hello.sh"]
    
    # hello.sh
    #!/bin/sh
    echo "容器启动于 $(date)"
    curl -s https://www.baidu.com -o /dev/null -w "HTTP状态码: %{http_code}\n"
    

    构建并运行:

    cd ~/docker-demo
    docker build -t my-hello:v1 .
    docker run --rm my-hello:v1
    # 输出类似:
    # 容器启动于 Mon Nov 20 10:15:33 UTC 2023
    # HTTP状态码: 200

    视频4:KodeKloud - Docker for the Absolute Beginner(Udemy免费章节)

    这个视频是英文的,我用1.5倍速看的。它讲的是Dockerfile的编写规范,比如为什么要把变化最频繁的代码放在Dockerfile最后。核心知识点是层缓存:Docker构建时,每一行指令都生成一层,如果某一行没变,后面的层直接使用缓存。

    看完做这个练习:对比不同Dockerfile写法的构建耗时。

    # 两种写法,假设代码目录里有node_modules(100MB)
    # 写法A:先COPY全部再npm install
    # 写法B:先COPY package.json再npm install再COPY全部代码
    
    # 我自己实测的数据(在 MacBook Pro 2021, Docker Desktop 4.25.2):
    # 写法A修改一次代码,重新build耗时 1m32s
    # 写法B修改一次代码,重新build耗时 12s
    # 这就是层缓存的作用

    第二阶段:解决「多容器协作」问题(视频5-7)

    视频5:TechWorld with Nana - Docker Compose Tutorial(约40分钟)

    单容器会了,真实项目至少需要:Nginx + PHP-FPM + MySQL + Redis。每个容器单独用docker run启动,你要写一长串 -v--network 参数,而且容器重启后IP会变。

    这个视频是整个Docker学习路线的分水岭:她用一个投票应用(Python/Node/Redis/PostgreSQL)演示Compose怎么一键拉起4个容器。看完你才会理解,docker-compose.yml就是一套多容器的启动说明书。

    看完做这个练习,用Compose启动LNMP架构。

    # 文件路径: ~/docker-demo/docker-compose.yml
    version: "3.8"
    
    services:
      nginx:
        image: nginx:1.25.3
        ports:
          - "8080:80"
        volumes:
          - ./nginx/conf.d:/etc/nginx/conf.d:ro
          - ./html:/usr/share/nginx/html
        depends_on:
          - php
        networks:
          - lnmp
    
      php:
        image: php:8.3.1-fpm
        volumes:
          - ./html:/usr/share/nginx/html
        networks:
          - lnmp
    
      mysql:
        image: mysql:8.0.35
        environment:
          MYSQL_ROOT_PASSWORD: root123
          MYSQL_DATABASE: demo
        ports:
          - "3307:3306"
        volumes:
          - mysql_data:/var/lib/mysql
        networks:
          - lnmp
    
      redis:
        image: redis:7.2.3
        networks:
          - lnmp
    
    volumes:
      mysql_data:
    
    networks:
      lnmp:
    cd ~/docker-demo
    docker compose up -d
    # 输出:
    # [+] Running 5/5
    # ✔ Network docker-demo_lnmp  Created
    # ✔ Volume docker-demo_mysql_data  Created
    # ✔ Container docker-demo-mysql-1  Started
    # ✔ Container docker-demo-redis-1  Started
    # ✔ Container docker-demo-php-1    Started
    # ✔ Container docker-demo-nginx-1  Started
    
    docker compose ps
    # 输出5个容器全部running

    注意:depends_on只控制启动顺序,不等待MySQL就绪。项目里PHP连接MySQL需要重试机制,这个问题下面避坑段细说。

    视频6:Bret Fisher - Docker Mastery(Udemy,看前6个Module免费)

    这个老哥做过20年运维,他的视频语速快,信息密度极大。前6个Module讲Dockerfile的最佳实践:多阶段构建、非root用户运行、镜像标签管理。

    看完做这个练习:把PHP镜像从1.2GB降到150MB。

    # 多阶段构建:第一阶段编译扩展,第二阶段只保留运行时
    # 构建PHP应用(以laravel为例)
    FROM php:8.3.1-cli AS builder
    
    # 安装依赖
    RUN apt-get update && apt-get install -y \
        libzip-dev unzip git \
        && docker-php-ext-install pdo_mysql zip
    
    # 复制composer并安装依赖
    COPY --from=composer:2.6.5 /usr/bin/composer /usr/bin/composer
    WORKDIR /app
    COPY composer.json composer.lock ./
    RUN composer install --no-dev --no-scripts --prefer-dist
    
    # 第二阶段:runtime
    FROM php:8.3.1-fpm
    
    RUN apt-get update && apt-get install -y \
        libzip-dev \
        && docker-php-ext-install pdo_mysql zip
    
    # 非root用户
    RUN useradd -m -u 1000 app
    USER app
    
    WORKDIR /app
    COPY --chown=app --from=builder /app /app
    COPY . .
    
    EXPOSE 9000
    CMD ["php-fpm"]
    
    # 构建并查看镜像大小
    docker build -t my-laravel:prod .
    
    docker images | grep my-laravel
    # 输出: my-laravel   prod  142MB   (优化前base镜像 1.2GB)
    # 构建时间: 首次 4m28s,第二次缓存命中 38s

    视频7:DevOps Directive - Dockerfile Best Practices(约30分钟)

    这个视频专门讲我不懂的 docker build 优化技巧。.dockerignore 排除不需要的文件,--cache-from 利用CI缓存。两个重点:

    1. 必须写 .dockerignore,否则本地 node_modules.git 目录会被打包进构建上下文,直接拖慢构建。
    2. 基础镜像标签不要用 latest,构建出来的镜像今天和明天不一样,后期没法复现。
    # .dockerignore 示例,放在项目根目录
    node_modules
    .git
    .gitignore
    *.log
    .env
    Dockerfile
    docker-compose.yml
    .vscode
    .idea

    第三阶段:上生产(视频8-10)

    视频8:Nana - Docker in Production(约50分钟)

    这个视频解决的是「容器部署到服务器之后怎么办」。包括:容器日志收集(json-file驱动)、资源限制(--memory--cpus)、容器自动重启策略。

    看完做这个练习:给容器加上资源限制和重启策略。

    # docker-compose.yml 增加资源限制
    services:
      php:
        image: php:8.3.1-fpm
        deploy:
          resources:
            limits:
              memory: 512M
              cpus: "0.50"
        restart: unless-stopped
        logging:
          driver: json-file
          options:
            max-size: "10m"
            max-file: "3"

    视频9:B站UP主「技术蛋」- Docker部署Web应用实战(约1小时)

    他是用 GitHub Actions + Docker 构建自动部署流程的。视频里演示了:push 代码 → GitHub Actions 构建镜像 → push 到阿里云镜像仓库 → SSH到服务器 → docker compose pull + up -d。整个流程26分钟,看完我当晚就搭了自己的CI/CD。

    这个流程的核心代码如下:

    # .github/workflows/deploy.yml
    name: Deploy
    
    on:
      push:
        branches: [ main ]
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          
          - name: Login to Aliyun ACR
            uses: docker/login-action@v3
            with:
              registry: registry.cn-hangzhou.aliyuncs.com
              username: ${{ secrets.ALIYUN_USERNAME }}
              password: ${{ secrets.ALIYUN_PASSWORD }}
              
          - name: Build and push
            run: |
              docker build -t registry.cn-hangzhou.aliyuncs.com/myapp/demo:${{ github.sha }} .
              docker push registry.cn-hangzhou.aliyuncs.com/myapp/demo:${{ github.sha }}
              
          - name: Deploy to server
            uses: appleboy/ssh-action@v1.0.0
            with:
              host: ${{ secrets.SERVER_HOST }}
              username: root
              key: ${{ secrets.SSH_KEY }}
              script: |
                cd /opt/myapp
                echo "IMAGE_TAG=${{ github.sha }}" > .env
                docker compose pull
                docker compose up -d

    视频10:KodeKloud - Docker Security(约40分钟)

    这个视频压轴。讲了两个我在生产环境必须处理的安全问题:容器以root运行的风险、镜像漏洞扫描。特别是镜像扫描,我能跑起来不代表镜像安全。

    看完做这个练习:用trivy扫描镜像漏洞。

    # 安装trivy(版本 v0.48.3)
    sudo apt-get install -y wget apt-transport-https
    wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
    echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
    sudo apt-get update
    sudo apt-get install -y trivy
    
    # 扫描本地镜像
    trivy image my-laravel:prod
    # 输出高危漏洞汇总(CVE编号+修复版本)

    完整代码实现:按10个视频学完后,我的一键部署脚本

    这套代码是我学完上述视频后,给一个PHP博客项目写的生产部署配置。它解决三个问题:一条命令启动整套环境、代码更新后分10秒完成重新构建、数据不丢。

    # 部署脚本 deploy.sh
    #!/bin/bash
    # 用法: ./deploy.sh {build|up|down|logs|update}
    # DOCKER_VERSION: 24.0.7, DOCKER_COMPOSE: v2.21.0
    
    set -e
    
    PROJECT_NAME="blog"
    COMPOSE_FILE="docker-compose.prod.yml"
    
    case "$1" in
      build)
        docker compose -f $COMPOSE_FILE build --no-cache
        ;;
      up)
        docker compose -f $COMPOSE_FILE up -d
        docker compose -f $COMPOSE_FILE ps
        ;;
      down)
        docker compose -f $COMPOSE_FILE down
        ;;
      logs)
        docker compose -f $COMPOSE_FILE logs -f --tail=100
        ;;
      update)
        # 拉取最新代码并重新构建
        git pull origin main
        docker compose -f $COMPOSE_FILE build
        docker compose -f $COMPOSE_FILE up -d
        # 清理旧镜像
        docker image prune -f
        ;;
      *)
        echo "Usage: $0 {build|up|down|logs|update}"
        exit 1
        ;;
    esac
    
    exit 0
    # docker-compose.prod.yml
    version: "3.8"
    
    services:
      nginx:
        image: nginx:1.25.3-alpine
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - ./nginx/conf.d:/etc/nginx/conf.d:ro
          - ./nginx/ssl:/etc/nginx/ssl:ro
          - app_files:/var/www/html:ro
        restart: unless-stopped
        networks:
          - backend
        depends_on:
          - php
    
      php:
        build:
          context: .
          dockerfile: Dockerfile.prod
        volumes:
          - app_files:/var/www/html
        restart: unless-stopped
        networks:
          - backend
        environment:
          - APP_ENV=production
          - APP_DEBUG=false
          - DB_HOST=mysql
          - REDIS_HOST=redis
        deploy:
          resources:
            limits:
              memory: 512M
              cpus: "0.50"
    
      mysql:
        image: mysql:8.0.35
        volumes:
          - mysql_data:/var/lib/mysql
          - ./mysql/init:/docker-entrypoint-initdb.d:ro
        environment:
          MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
          MYSQL_DATABASE: blog
          MYSQL_USER: blog
          MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        restart: unless-stopped
        networks:
          - backend
    
      redis:
        image: redis:7.2.3-alpine
        command: redis-server --requirepass ${REDIS_PASSWORD}
        restart: unless-stopped
        networks:
          - backend
    
    volumes:
      mysql_data:
      app_files:
    
    networks:
      backend:

    这套配置里值得注意:php的代码目录和nginx的html共用同一个volume。nginx容器用只读挂载,php容器用读写挂载。这样php写完文件nginx能直接读到,不需要重新build。

    效果数据:我学完这套路线的真实收益

    下面这些数据是我在2023年11月部署一个PHP博客项目时实测的。

    指标手动部署Docker Compose部署提升
    首次从零搭建环境3小时56分(含踩坑)11分23秒(含镜像拉取)快95%
    代码更新后重新发布手动git pull + PHP没缓存 + 重启php-fpm,约8分钟docker compose build + up -d,约1分40秒快79%
    切换分支到另一个项目改Nginx配置、改PHP扩展、改MySQL连接,约40分钟docker compose -p other up -d,约2分钟快95%
    MySQL数据备份mysqldump 手动1小时+docker run --rm volumn挂载 + mysqldump,2分钟快96%

    镜像体积优化(多阶段构建):从1.2GB降到142MB。构建速度:首次构建4分28秒,之后缓存命中38秒。这是Bret Fisher那个视频教的多阶段构建+层缓存的实际效果。

    还有一个冷数据:生产服务器的内存占用从2.1GB降到880MB。因为手动部署的PHP-FPM进程数管控不严,容器化之后每个进程限制512MB,Nginx限制256MB,MySQL限制1.5GB,整体可预期。

    避坑:我学Docker过程中实际踩过的5个坑

    坑1:Docker Hub拉镜像被墙

    安装Docker之后第一件事就是配镜像加速器。不配的话ubuntu镜像可能拉3个小时超时。国内可用地址:阿里云(需要登录获取专属地址)、中科大(docker.mirrors.ustc.edu.cn)。配置方法如下:

    # 修改 /etc/docker/daemon.json
    sudo mkdir -p /etc/docker
    sudo tee /etc/docker/daemon.json <<'EOF'
    {
      "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
    }
    EOF
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    
    # 验证:
    docker pull alpine:3.19
    # 应该10秒内完成

    2024年之后中科大的Docker镜像也关停了,建议直接配阿里云容器镜像加速器。

    坑2:容器时区和宿主机不一致

    默认容器UTC时区,比北京时间慢8小时。PHP日志和MySQL日志时间全偏。解决办法有两种:

    # Dockerfile 里加一行
    RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
        && echo "Asia/Shanghai" > /etc/timezone
    # 或者compose里挂载时区文件
    services:
      php:
        volumes:
          - /etc/localtime:/etc/localtime:ro
          - /etc/timezone:/etc/timezone:ro

    坑3:挂载卷导致文件权限变成root

    容器内php进程以www-data运行,向宿主机挂载目录写文件时,宿主机看到的owner是1000。删文件必须sudo。我经常做的事:在宿主机执行 sudo rm -rf 清缓存。后来改用命名卷(named volume)而不是bind mount,权限问题少很多,但开发时可以先用bind mount方便调试。

    坑4:docker compose up 之后MySQL还没就绪

    PHP启动时连接MySQL会报Connection refused。depends_on 只管启动顺序,不管服务是否ready。我的解决方式:给PHP容器加一个等待脚本。

    #!/bin/sh
    # wait-for-mysql.sh
    set -e
    
    host="$1"
    shift
    cmd="$@"
    
    until mysql -h "$host" -P 3306 -u root -p"$MYSQL_ROOT_PASSWORD" -e 'select 1' >/dev/null 2>&1; do
      echo "MySQL 未就绪,等待3秒..."
      sleep 3
    done
    
    exec $cmd
    # compose里覆盖entrypoint
    services:
      php:
        entrypoint: ["/bin/sh", "-c"]
        command: ["./wait-for-mysql.sh mysql php-fpm"]

    坑5:不小心删了容器,MySQL数据全没了

    一开始我不懂数据卷,docker-compose.yml里没写volumes。第一次执行 docker compose down 再up,发现项目所有用户数据消失。当时慌了五分钟。解决办法就是MySQL一定要用命名卷:

    services:
      mysql:
        volumes:
          - mysql_data:/var/lib/mysql
    volumes:
      mysql_data:

    命名卷的生命周期独立于容器。docker compose down 不会删卷,只有 docker compose down -v 才会删。误执行了 -v 的话,用 docker run --volumes-from + extundelete 有可能救回来,但是非常费劲。我已经把 down -v 这个命令从我的肌肉记忆里抹掉了。

    补充:从10个视频到真正干活,还差什么

    看视频只是把路走通,你在实际项目里一定会遇到这几位:健康检查(healthcheck)、容器编排(Kubernetes或Swarm)、多环境配置(dev/staging/prod)。我的路线是:先把compose练熟,再了解Swarm,最后上Kubernetes。别跳过前面直接学Kubernetes,你连Pod和Deployment的关系都要理解半天。

    最后说一句:如果你把这10个视频看完、10个练习做完,你的Docker水平足够应付90%的日常开发需求。剩下的10%,靠 docker system dfdocker logs 慢慢养成习惯。

    祝各位少踩坑,把省下来的时间用来摸鱼。