阅读视图

发现新文章,点击刷新页面。

Docker安装(非sudo用户可用)

Docker 安装说明 (Ubuntu/Debian)

本文档提供在 Ubuntu/Debian 系统上安装 Docker 并配置非 sudo 运行的完整步骤。

目录


系统要求

  • 操作系统:Ubuntu 20.04/22.04/24.04 或 Debian 10/11/12
  • 架构:x86_64 (amd64) 或 arm64
  • 权限:需要 sudo 权限进行安装

安装前检查

1. 检查系统信息

# 查看系统版本
lsb_release -a

# 查看系统架构
dpkg --print-architecture

2. 检查是否已安装 Docker

# 检查 Docker 版本
docker --version

# 检查 Docker 服务状态
systemctl status docker

# 检查是否能运行容器
docker ps

如果显示 command not found,说明 Docker 未安装。

3. 卸载旧版本(如存在)

# 卸载旧版本
sudo apt-get remove -y docker docker-engine docker.io containerd runc

安装 Docker

方法一:使用官方脚本安装(推荐,简单快捷)

# 下载并运行官方安装脚本
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# 清理安装脚本
rm get-docker.sh

方法二:手动安装(推荐,更可控)

步骤 1:更新软件包索引
sudo apt-get update
步骤 2:安装必要的依赖包
sudo apt-get install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
步骤 3:添加 Docker 官方 GPG 密钥
# 创建 keyrings 目录
sudo install -m 0755 -d /etc/apt/keyrings

# 下载并添加 GPG 密钥
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

注意:如果是 Debian 系统,将 URL 中的 ubuntu 替换为 debian

步骤 4:添加 Docker 软件源
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

注意:如果是 Debian 系统,将 URL 中的 ubuntu 替换为 debian

步骤 5:安装 Docker Engine
# 更新软件包索引
sudo apt-get update

# 安装 Docker 组件
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

配置非 sudo 运行

默认情况下,Docker 守护进程绑定到 Unix socket 而非 TCP 端口,该 socket 由 root 用户拥有,因此需要 sudo 权限。

步骤 1:创建 docker 用户组

# 创建 docker 组(通常安装时已自动创建)
sudo groupadd docker

步骤 2:将当前用户添加到 docker 组

# 将当前用户添加到 docker 组
sudo usermod -aG docker $USER

# 查看当前用户所属的组
groups $USER

步骤 3:使组权限生效

有两种方法使组权限生效:

方法 1:重新登录(推荐)

# 注销并重新登录系统
# 或者重启系统
sudo reboot

方法 2:使用 newgrp 临时生效

# 在当前终端临时生效
newgrp docker

步骤 4:启动 Docker 服务

# 启动 Docker 服务
sudo systemctl start docker

# 设置开机自启动
sudo systemctl enable docker

# 检查服务状态
sudo systemctl status docker

验证安装

1. 检查 Docker 版本

docker --version

预期输出类似:

Docker version 24.0.7, build afdd53b

2. 检查 Docker 服务状态

sudo systemctl status docker

3. 运行测试容器(验证非 sudo)

# 运行 hello-world 测试镜像
docker run hello-world

预期输出:

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

4. 检查 Docker 信息

docker info

5. 验证 Docker Compose

docker compose version

安装 Docker Compose

Docker Compose 现已作为 Docker 的插件集成,通过 docker compose 命令使用。

如果需要独立版本的 docker-compose 命令:

# 下载最新版本(替换版本号)
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

常见问题排查

问题 1:权限被拒绝 (Permission denied)

错误信息

permission denied while trying to connect to the Docker daemon socket

解决方案

# 方法 1:重新加载组权限
newgrp docker

# 方法 2:修改 socket 权限(临时)
sudo chmod 666 /var/run/docker.sock

# 方法 3:重新登录系统使组权限生效

问题 2:Docker 服务未启动

错误信息

Cannot connect to the Docker daemon. Is the docker daemon running on this host?

解决方案

# 启动 Docker 服务
sudo systemctl start docker

# 检查服务状态
sudo systemctl status docker

# 查看日志
sudo journalctl -u docker

问题 3:网络问题导致下载失败

解决方案

# 使用国内镜像源(阿里云)
# 编辑 Docker 配置
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://docker.mirrors.ustc.edu.cn"
  ]
}
EOF

# 重启 Docker 服务
sudo systemctl daemon-reload
sudo systemctl restart docker

问题 4:GPG 密钥问题

解决方案

# 删除旧密钥
sudo rm /etc/apt/keyrings/docker.gpg

# 重新添加密钥
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

问题 5:用户组未生效

解决方案

# 检查用户是否在 docker 组
groups $USER

# 如果不在,重新添加
sudo usermod -aG docker $USER

# 完全注销并重新登录
logout
# 或重启系统
sudo reboot

卸载 Docker

如需卸载 Docker:

# 卸载 Docker 包
sudo apt-get purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

# 删除所有镜像、容器和数据卷
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

# 删除 Docker 源
sudo rm /etc/apt/sources.list.d/docker.list

# 删除 GPG 密钥
sudo rm /etc/apt/keyrings/docker.gpg

参考链接

React Context 详解:从入门到性能优化

React Context 详解:从入门到性能优化

本文适合熟悉 Vue 但刚开始学习 React 的开发者,通过 Vue 的 provide/inject 对比来理解 React Context。

一、什么是 Context?

在组件开发中,我们经常遇到这样的场景:某个数据需要在多层嵌套的组件间共享。如果一层层通过 props 传递,代码会变得非常冗长且难以维护,这就是所谓的 "prop drilling" 问题。

React 的 Context 和 Vue 的 provide/inject 都是为了解决这个问题而设计的 —— 它们允许数据跨层级传递,跳过中间组件。

二、React Context 基础用法

核心三步

  1. 创建 Context —— 创建一个数据共享的"通道"
  2. 提供数据 —— 父组件通过 Provider 提供数据
  3. 消费数据 —— 子组件通过 useContext 获取数据

完整示例

// ========== 1. 创建 Context ==========
// context.tsx
import { createContext, useContext } from 'react'

// 定义数据类型
type MyContextValue = {
  name: string
  age: number
}

// 创建 Context(可设置默认值)
const MyContext = createContext<MyContextValue>({ name: '', age: 0 })

// 导出一个 hook 方便使用
const useMyContext = () => useContext(MyContext)

export { MyContext, useMyContext }
// ========== 2. 父组件提供数据 ==========
// parent.tsx
import { MyContext } from './context'
import Child from './child'

const Parent = () => {
  const data = { name: '张三', age: 18 }

  return (
    <MyContext.Provider value={data}>
      <Child />
    </MyContext.Provider>
  )
}
// ========== 3. 子组件消费数据 ==========
// child.tsx
import { useMyContext } from './context'

const Child = () => {
  const { name, age } = useMyContext()
  
  return <div>{name} - {age}岁</div>
}

三、对比 Vue 的 provide/inject

如果你熟悉 Vue,这个概念其实非常相似:

步骤 React Vue
创建 createContext() 无需显式创建
提供 <Context.Provider value={}> provide(key, value)
消费 useContext(Context) inject(key)

Vue 等价写法

<!-- 父组件 -->
<script setup>
import { provide } from 'vue'
import Child from './child.vue'

const data = { name: '张三', age: 18 }
provide('myContext', data)
</script>

<template>
  <Child />
</template>
<!-- 子组件 -->
<script setup>
import { inject } from 'vue'

const { name, age } = inject('myContext')
</script>

<template>
  <div>{{ name }} - {{ age }}岁</div>
</template>

可以看到,两者的设计思想是一致的,只是语法不同:

  • React 使用 JSX 的组件包裹方式 <Context.Provider>
  • Vue 使用 Composition API 的函数调用方式

四、原生 Context 的性能问题

原生 React Context 存在一个性能陷阱:

只要 Context value 中的任何一个字段变化,所有消费这个 Context 的组件都会重新渲染,即使它们只用到了没变的字段。

// 原生 React Context 的问题
const MyContext = createContext({ name: '张三', age: 18, city: '北京' })

// 这个组件只用 name,但 age 或 city 变化时也会重新渲染!
const Child = () => {
  const { name } = useContext(MyContext)
  return <div>{name}</div>
}

当 Context 中有几十个字段时(这在大型应用中很常见),这个问题会严重影响性能。

五、use-context-selector:性能优化方案

为了解决这个问题,社区提供了 use-context-selector 库。它支持选择器模式,让组件只订阅自己关心的字段。

安装

npm install use-context-selector

使用方式

// 从 use-context-selector 导入,而不是 react
import { createContext, useContext } from 'use-context-selector'

const MyContext = createContext({ name: '张三', age: 18, city: '北京' })

// 使用选择器,只订阅 name
const Child = () => {
  const name = useContext(MyContext, v => v.name)  // age 或 city 变化不会触发重渲染
  return <div>{name}</div>
}

核心区别

特性 React 原生 use-context-selector
导入来源 'react' 'use-context-selector'
更新粒度 整个 Context 变化就重渲染 可以用选择器精确订阅某个字段
性能 大型 Context 可能性能差 优化了选择器模式,避免不必要的重渲染
使用方式 useContext(ctx) useContext(ctx, selector?)

六、实际案例分析

以 Dify 项目中的 ChatWithHistoryContext 为例:

// context.tsx
import { createContext, useContext } from 'use-context-selector'

export type ChatWithHistoryContextValue = {
  appMeta?: AppMeta | null
  appData?: AppData | null
  appParams?: ChatConfig
  currentConversationId: string
  conversationList: AppConversationData['data']
  handleNewConversation: () => void
  handleChangeConversation: (conversationId: string) => void
  // ... 还有 20+ 个字段
}

export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
  currentConversationId: '',
  // ... 默认值
})

export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)
// parent.tsx - 提供数据
const ChatWithHistoryWrap = () => {
  const contextValue = useChatWithHistory()  // 获取所有数据

  return (
    <ChatWithHistoryContext.Provider value={contextValue}>
      <ChatWithHistory />
    </ChatWithHistoryContext.Provider>
  )
}
// child.tsx - 消费数据
const ChatWithHistory = () => {
  const { 
    appData, 
    conversationList, 
    handleChangeConversation 
  } = useChatWithHistoryContext()
  
  // 使用数据...
}

这个 Context 有 30+ 个字段,如果使用原生 Context,任何一个字段变化都会导致所有子组件重渲染。使用 use-context-selector 后,框架内部做了优化,避免了不必要的渲染。

七、数据流图解

┌─────────────────────────────────────────────────┐
│  ChatWithHistoryWrap (父组件)                    │
│                                                 │
│  通过 useChatWithHistory() 获取所有数据          │
│  { appData, appParams, conversationList, ... }  │
└─────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────┐
│  ChatWithHistoryContext.Provider                │
│  value={{ appData, appParams, ... }}            │  ← 数据注入到 Context
└─────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────┐
│  ChatWithHistory (子组件)                        │
│                                                 │
│  useChatWithHistoryContext() 获取数据           │
└─────────────────────────────────────────────────┘
                        │
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
     ┌─────────┐  ┌─────────┐  ┌─────────┐
     │ Sidebar │  │ Header  │  │ ChatWrap│
     └─────────┘  └─────────┘  └─────────┘
          │             │             │
          └─────────────┴─────────────┘
                        │
              孙组件同样可以通过
           useChatWithHistoryContext() 获取数据

八、最佳实践

  1. 小型项目:使用原生 React Context 即可,简单直接
  2. 大型项目:当 Context 字段较多(10+)时,考虑使用 use-context-selector
  3. 拆分 Context:如果可能,将不相关的数据拆分到不同的 Context 中
  4. 命名规范:导出一个自定义 hook(如 useMyContext),统一消费方式

九、总结

场景 推荐方案
简单数据共享 React 原生 Context
大型 Context,字段多 use-context-selector
Vue 背景开发者 理解为 provide/inject 的 React 版本

Context 本质上就是 跨层级传递数据 的工具,理解了这一点,无论是 React 还是 Vue,核心概念都是相通的。

❌