• 正文
  • 相关推荐
申请入驻 产业图谱

如何在Ubuntu安装Docker

10小时前
68
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

Docker提供了一个轻量级的虚拟化解决方案。相比于虚拟机,Docker容器更加轻便、快速、容易部署。

使用Docker,开发者可以避免在两台计算机上运行效果不一致的问题,容器提供了一致的行为,进而使得应用程序在不同环境的计算机上有相同的运行效果。

1 Docker环境安装步骤

1.1 安装依赖

更新系统包索引

sudo apt update && sudo apt upgrade -y

安装依赖

sudo apt install ca-certificates curl gnupg lsb-release -y

添加 Docker 官方 GPG 密钥

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

这里可能会报错:

xxpcb@xxpcb-ubuntu20:~/Desktop$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to download.docker.com:443 
gpg: no valid OpenPGP data found.
xxpcb@xxpcb-ubuntu20:~/Desktop$ 

国内虚拟机访问 download.docker.com 被重置,公钥下载失败。换阿里云 Docker 公钥 + 源,先创建 keyrings 目录

sudo mkdir -p /usr/share/keyrings

下载阿里云镜像的 Docker GPG 密钥

curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

添加 Docker 软件源(阿里云)

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

如果不需要使用国内镜像,使用下面 Docker 软件源

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

1.2 安装 Docker Engine

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

把当前用户加入 docker 组

sudo usermod -aG docker $USER

1.3 验证安装

docker --version
sudo docker run hello-world

我的版本

xxpcb@xxpcb-ubuntu20:~/Desktop$ docker --version
Docker version 28.1.1, build 4eba377

1.4 hello-world例子报错解决

hello-world如果报错:

xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker run hello-world
[sudo] password for xxpcb: 
Unable to find image 'hello-world:latest' locally

更换国内镜像

sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io"
  ]
}
EOF

验证 JSON 语法(防止写错)没有报错代表 JSON 格式正确。

python3 -m json.tool /etc/docker/daemon.json

重启docker

sudo systemctl daemon-reload
sudo systemctl restart docker

检查 docker 状态

sudo systemctl status docker

看到 active (running) 代表成功启动:

xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-10-12 22:44:21 CST; 8s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 5278 (dockerd)
      Tasks: 10
     Memory: 21.0M
     CGroup: /system.slice/docker.service
             └─5278 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.395932315+08:00" level=info msg="Loading containers: start."
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.710016808+08:00" level=warning msg="Error (Unable to complete atomic operation, key modified) deleting object [endpoint_c>
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.768697620+08:00" level=warning msg="error locating sandbox id fac6e7f687c85e304cfc1279d08f760ac1d552cb17b7e7c496a571951a4>
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.768799573+08:00" level=info msg="Loading containers: done."
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.794431058+08:00" level=info msg="Docker daemon" commit=01f442b containerd-snapshotter=false storage-driver=overlay2 versi>
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.794871375+08:00" level=info msg="Initializing buildkit"
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.818622645+08:00" level=info msg="Completed buildkit initialization"
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.828071241+08:00" level=info msg="Daemon has completed initialization"
Oct 12 22:44:21 xxpcb-ubuntu20 dockerd[5278]: time="2026-10-12T22:44:21.828227727+08:00" level=info msg="API listen on /run/docker.sock"
Oct 12 22:44:21 xxpcb-ubuntu20 systemd[1]: Started Docker Application Container Engine.
xxpcb@xxpcb-ubuntu20:~/Desktop$ 

然后可以先拉取hello-world然后再执行

sudo docker pull hello-world
sudo docker run hello-world

这次可以了

xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
4f55086f7dd0: Pull complete 
Digest: sha256:5e23090353324d887c48ad5e5c56d294eab81588df9605b07d1afe895f9cc8f8
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

xxpcb@xxpcb-ubuntu20:~/Desktop$ 

2 查看镜像与容器

2.1 Docker 引擎、镜像、容器的区别

Docker(引擎):刚才装的整套程序(dockerd 服务、docker 命令),是运行容器的底层软件,就像虚拟机软件 VMware。

Docker 镜像 (image):一个只读模板,相当于虚拟机的系统安装盘 / 模板。镜像不能直接 “进去”,镜像只是文件包。

Docker 容器 (container):用镜像启动出来的运行实例,相当于开机跑起来的虚拟机。

只能进入【容器】,不能直接进入镜像。

关系:在Docker引擎的支持下,镜像 → docker run → 生成容器,进入的是容器。

镜像获取两种方式:

docker pull:从镜像仓库(docker hub、国内镜像站)拉取

docker load -i xxx.tar:导入本地导出的镜像文件(docker save 导出的 tar 包)

2.2 查看镜像

比如查看镜像

sudo docker images

应该有刚才的hello-world镜像

xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker images
[sudo] password for xxpcb: 
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
hello-world     latest    e2ac70e7319a   6 months ago   10.1kB
hhb4tools/hhb   2.4.5     58df969ae05a   3 years ago    8.16GB
xxpcb@xxpcb-ubuntu20:~/Desktop$

参数说明:

REPOSITORY:仓库名

格式:名字 或者 用户名/名字

(Docker Hub 风格)同一个仓库名,可以有多个不同 TAG 的版本

TAG:标签,镜像的版本号,用来区分同一个仓库下不同版本

latest:默认标签,不写 tag 时默认拉取 latest(不一定是最新,只是默认别名)

2.4.5:hhb 镜像的版本号组合起来 hhb4tools/hhb:2.4.5 才是镜像完整唯一名称

IMAGE ID:镜像的唯一指纹

sha256 哈希的前 12 位,镜像真正的身份证

如果两个镜像 ID 一样,代表这两个镜像是完全相同的文件,只是名字 / 标签不同(镜像软链接的感觉)

一个 IMAGE ID 可以对应多个 REPOSITORY+TAG(镜像打别名)

CREATED:镜像构建时间

      • 镜像做好之后,这个时间就固定不变多次启动容器,镜像的 CREATED 不会变

SIZE:镜像占用本地磁盘大小

这是镜像总大小,不是容器大小Docker 镜像采用分层存储,多个镜像共享相同底层层时,磁盘实际占用会比所有 size 相加更小

注:hhb4tools/hhb是之前安装的玄铁 RISC-V 交叉编译器的镜像。

2.3 查看容器

xxpcb@xxpcb-ubuntu20:~/Desktop$ docker ps -a
CONTAINER ID  IMAGE                COMMAND   CREATED      STATUS                  PORTS   NAMES
211b48b192b2  hello-world          "/hello"  2 days ago   Exited (0) 2 days ago           tender_volhard
9b266aa1e9ac  hhb4tools/hhb:2.4.5  "bash"    3 years ago  Exited (0) 2 years ago          your.hhb2.4
xxpcb@xxpcb-ubuntu20:~/Desktop$ 

参数说明:

CONTAINER ID :容器 ID,唯一标识,注意与镜像ID不是一回事

IMAGE:这个容器基于哪个镜像启动hhb4tools/hhb:2.4.5,对应docker images的REPOSITORY:TAG

COMMAND:容器启动时执行的入口命令

比如执行 docker run ... bash,这里就会显示 "bash"

镜像默认入口比如/hhb/entry.sh

CREATED:容器创建时间

2 hours ago:这个容器是 2 小时前创建的(不是镜像创建时间!)

STATUS:容器当前状态

Up 10 minutes:正在运行,已经跑了 10 分钟

Exited (0) 5 min ago:已经退出,5 分钟前停止;括号 0 = 正常退出,非 0 是异常崩溃

Created:创建完成,但从来没启动过

Dead:容器损坏,只能删除

PORTS:端口映射(宿主机 ↔ 容器内端口)

空 PORTS,启动命令没有 -p,无端口映射

      • 有映射示例:0.0.0.0:8080->80/tcp,宿主机的 8080 端口,转发到容器内部 80 端口

NAMES:容器名字

        • 不手动指定

      --name

    • 时 Docker 自动随机生成,也可以自己命名;名字和 ID 等效,可以用来操作容器

3 创建自定义镜像

自定义镜像,实际也是要在一个基础镜像上,进行自定义的配置后,再次commit,得到自定义的镜像。

比如想创建一个有特定交叉工具链的镜像,可以以ubuntu或是其它linux镜像为基础,本篇使用debian来演示。

3.1 创建基础版 Dockerfile

以debian:bullseye-slim为基础,先只装基础编译依赖,交叉工具链可以后续手动继续配置。

新建文件夹,创建 Dockerfile文件

FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive

# 安装基础工具:编译依赖、wget、tar、vim等,清理apt缓存减小镜像
RUN apt update && apt install -y 
    build-essential 
    cmake 
    git 
    wget 
    tar 
    unzip 
    vim 
    && rm -rf /var/lib/apt/lists/*

# 容器内默认工作目录
WORKDIR /workspace

3.2 构建镜像

在 Dockerfile 所在目录执行:

docker build -t debian-arm64-base:v1 .

构建完成,debian-arm64-base:v1 就是我们的基础镜像,里面只有 debian 系统 + 基础编译软件。

第一次构建的时候,可能是海外网站访问问题,计时一直在增加,但是下载的大小一直

Ctrl+C结束掉,然后在Dockfile的RUN apt前面一行增加:

RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

再次构建还是报错:

改为如下:

FROM debian:bookworm-slim

RUN echo "deb http://mirrors.ustc.edu.cn/debian bookworm main contrib non-free" > /etc/apt/sources.list && 
echo "deb http://mirrors.ustc.edu.cn/debian bookworm-updates main contrib non-free" >> /etc/apt/sources.list && 
echo "deb http://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free" >> /etc/apt/sources.list

# 安装编译依赖
RUN apt update && apt install -y 
    build-essential 
    cmake 
    git 
    wget 
    tar 
    unzip 
    vim 
    && rm -rf /var/lib/apt/lists/*

# 后续你可以在这里添加ARM64交叉编译工具链
# RUN wget xxx 安装aarch64-linux-gnu-gcc

然后构建前清理旧缓存

docker build --no-cache -t debian-arm64-base:v1 .

这次可以了:

可以查看一下:

xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED         SIZE
debian-arm64-base   v1        24de33851c6b   9 minutes ago   604MB
hello-world         latest    e2ac70e7319a   6 months ago    10.1kB
hhb4tools/hhb       2.4.5     58df969ae05a   3 years ago     8.16GB
xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$ 

注意到,这次构建镜像的时候,Dockerfile忘记加上WORKDIR /workspace这句了,也就是没有指定默认的工作目录。可以在docker run的再次指定即可。

3.3 启动容器

docker run -it --name build-arm64-env -w /workspace -v /home/xxpcb/myTest/DockerTest/code:/workspace debian-arm64-base:v1 bash

--name build-arm64-env:给容器命名,后面拷贝文件、commit 打包都靠这个名字

-it:执行完直接进入容器终端

确认创建的容器

xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$ docker ps -a
CONTAINER ID   IMAGE                  COMMAND    CREATED              STATUS                     PORTS     NAMES
6ef569725d63   debian-arm64-base:v1   "bash"     About a minute ago   Exited (0) 9 seconds ago             build-arm64-env
211b48b192b2   hello-world            "/hello"   5 days ago           Exited (0) 5 days ago                tender_volhard
9b266aa1e9ac   hhb4tools/hhb:2.4.5    "bash"     3 years ago          Exited (0) 2 years ago               your.hhb2.4
xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$ 

3.4 docker run的参数完整说明

这里补充说明一下 docker run的各种参数

3.4.1 高频参数

参数 含义 示例 说明
-i interactive,保持标准输入打开 docker run -i ubuntu bash 不加则 bash 输入被关闭,交互命令无法输入
-t tty,分配伪终端 docker run -t ubuntu bash 提供命令行提示符;通常与 - i 连用写成-it
-it 交互式终端,进入容器 shell docker run -it debian-arm-base:v1 bash 编译、调试最常用组合
--name 指定容器名称 --name arm-build-env 不写则随机生成;后面 cp/stop/commit 都用名字定位
-v 挂载目录(宿主机:容器) -v /home/xxpcb/code:/workspace 源码宿主机,编译容器内,产物落宿主机,容器删代码不丢
-d 后台运行(detach) docker run -d nginx 放后台守护,不占终端;交互编译不用它
--rm 容器退出后自动删除 docker run -it --rm ubuntu bash 临时测试用完即焚;⚠️要做 commit 打包绝不能加

3.4.2 常用参数

参数 含义 示例 说明
-p 端口映射(宿主:容器) -p 8080:80 容器 80→宿主机 8080;交叉编译一般用不到
-e 注入环境变量 -e PATH="/opt/bin:$PATH" 可写多个-e
-w / --workdir 容器内工作目录 -w /workspace 启动后直接进入该目录,省去手动 cd
--network 网络模式 --network=host host = 共享宿主机网络;默认 bridge,编译场景够用

3.4.3 其它

参数 含义 说明
--privileged 特权模式 接近宿主机 root 权限,安全风险大,尽量不开
--user 指定容器内运行用户 默认 root
-m / --memory 限制最大内存 如 -m 2G
--cpus 限制 CPU 核数 如 --cpus 2,编译时可控制资源占用

3.5 退出容易后的再次进入指令

情况 1:容器只是退出

已经停止,最常见,exit 之后

# 第一步:启动这个容器
docker start build-arm64-env

# 第二步:进入容器终端
docker exec -it build-arm64-env bash

情况 2:容器还在运行

只是你新开终端,想进去

直接执行这一条:

docker exec -it build-arm64-env bash

docker 三指令对比

命令 作用 区别
docker run 新建容器并启动 每次 run 都生成新容器
docker start 容器名 启动已存在的旧容器 不新建
docker exec -it 容器名 bash 进入正在运行的容器 要求容器已在跑

3.4 安装交叉工具链

3.4.1 下载与安装

# 进入opt目录
cd /opt

# 解压工具链
tar xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar

# 临时添加环境变量(当前这个bash会话生效,容器重启会失效)
export PATH="/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH"

# 验证工具链
aarch64-linux-gnu-g++ -v

# 把PATH写入 /etc/profile,每次启动容器自动加载
echo 'export PATH="/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH"' >> /etc/profile

配置好后,可以再用另一个shell进入docker试下

注意:docker exec 进入的 bash 默认是交互式 shell,但不会自动加载 /etc/profile!

/etc/profile 只在**登录 shell(login shell)**才会执行。

可以exec 的时候,用登录 shell 方式进入:

docker exec -it build-arm64-env bash -l

bash -l = login shell,会加载 /etc/profile,PATH 就自动带上交叉工具链路径

3.4.2 编译程序验证

使用刚才安装的交叉编译工具链,编译出程序,放到对应的板子上,验证是否可运行,这里就先不演示了。

3.5 打包成新镜像

退出容器(容器不会被删除,只是退出 shell)

exit

宿主机终端执行 commit,把当前容器状态打包成新镜像

docker commit build-arm64-env debian-arm64-base:v2

build-arm64-env:容器名字

debian-arm64-base:v2:你新生成的镜像名字 + tag

可以看到刚才commit的v2镜像了,有1.39GB,具体多了什么导致大了好多,后续再探究。

3.6 使用新镜像再次创建容器

使用新镜像再次创建容器,看看刚才手动安装的交叉工具链,是不是使用v2镜像创建的容器就默认自带了。

docker run -it --name build-arm64-env-v2 -w /workspace -v /home/xxpcb/myTest/DockerTest/code:/workspace debian-arm64-base:v2 bash

使用新的窗口启动:

docker exec -it build-arm64-env-v2 bash -l

可以看到v2的容器已经自带了刚才在v1容器中安装的交叉编译工具链了。

4 总结

本篇介绍了Docker的一些概念与操作,首先是在Ubuntu虚拟机中安装Docker引擎,然后先简单体验helloword容器,介绍镜像与容器的区别,接着尝试创建自定义的镜像,安装自定义的交叉编译工具链,最后使用自定义的镜像,再次创建容器进行验证。

相关推荐

控制科学与工程硕士,日常分享单片机、嵌入式、C/C++、Linux等学习经验干货~