004-Solidity 开发环境的 Docker 安装与配置完全指南

Solidity 开发环境的 Docker 安装与配置完全指南

Docker 为 Solidity 开发提供了一个隔离、一致且可移植的环境,使您能够避免版本冲突并快速开始开发。本指南将详细介绍如何使用 Docker 安装和配置 Solidity 开发环境。

目录

  1. Docker 基础安装
  2. Solidity 编译器的 Docker 镜像
  3. 完整开发环境的 Docker 配置
  4. 使用 Docker Compose 管理开发环境
  5. 开发工作流与最佳实践
  6. 高级配置与优化
  7. 常见问题与解决方案
  8. 附录:有用的 Docker 命令

1. Docker 基础安装

在开始使用 Docker 进行 Solidity 开发之前,您需要安装 Docker 引擎。

Windows 安装

  1. 下载并安装 Docker Desktop for Windows
  2. 启动 Docker Desktop
  3. 打开终端,验证安装是否成功:

    bash

    docker --version
    

macOS 安装

  1. 下载并安装 Docker Desktop for Mac
  2. 启动 Docker Desktop
  3. 打开终端,验证安装是否成功:

    bash

    docker --version
    

Linux 安装

对于 Ubuntu:

bash

# 更新包索引
sudo apt-get update

# 安装必要的依赖
sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release

# 添加 Docker 的官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 设置稳定版仓库
echo "deb [arch=amd64 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

# 更新包索引
sudo apt-get update

# 安装 Docker Engine
sudo apt-get install docker-ce docker-ce-cli containerd.io

# 验证安装
docker --version

其他 Linux 发行版请参考 Docker 官方文档

安装 Docker Compose

Docker Compose 用于定义和运行多容器 Docker 应用程序:

Windows/macOS: Docker Desktop 已包含 Docker Compose

Linux:

bash

# 下载当前稳定版本
sudo curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 应用可执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

2. Solidity 编译器的 Docker 镜像

以太坊官方提供了 Solidity 编译器的 Docker 镜像,可以快速使用特定版本的编译器。

拉取官方 Solidity 编译器镜像

bash

# 拉取最新版
docker pull ethereum/solc:stable

# 拉取特定版本(推荐用于生产环境)
docker pull ethereum/solc:0.8.17

使用 Solidity 编译器

编译单个文件:

bash

docker run --rm -v $(pwd):/sources ethereum/solc:0.8.17 /sources/SimpleStorage.sol

编译并输出 ABI 和二进制文件:

bash

docker run --rm -v $(pwd):/sources ethereum/solc:0.8.17 --abi --bin /sources/SimpleStorage.sol -o /sources/build

参数说明:

  • --rm: 容器停止后自动删除
  • -v $(pwd):/sources: 将当前目录挂载到容器的 /sources 目录
  • --abi --bin: 输出 ABI 和二进制文件
  • -o /sources/build: 输出到 /sources/build 目录

3. 完整开发环境的 Docker 配置

创建自定义 Solidity 开发环境 Dockerfile

创建一个名为 Dockerfile 的文件,内容如下:

dockerfile

# 使用 Node.js 作为基础镜像
FROM node:16-bullseye-slim

# 设置工作目录
WORKDIR /app

# 安装基本工具
RUN apt-get update && apt-get install -y \
    git \
    curl \
    python3 \
    python3-pip \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# 安装 Solidity 编译器
RUN npm install -g [email protected]

# 安装 Hardhat、Truffle 和其他常用工具
RUN npm install -g hardhat truffle ganache @openzeppelin/contracts-ethereum-package eth-gas-reporter solidity-coverage

# 安装额外的分析工具
RUN pip3 install slither-analyzer solc-select mythril

# 预先安装常用依赖,加快后续构建
COPY package.json* package-lock.json* /tmp/
RUN cd /tmp && npm install && mkdir -p /app/node_modules && cp -R /tmp/node_modules/. /app/node_modules/

# 设置默认命令
CMD ["bash"]

构建自定义镜像

bash

docker build -t solidity-dev:latest .

使用自定义开发环境

创建新的 Hardhat 项目:

bash

docker run --rm -it -v $(pwd):/app solidity-dev npx hardhat init

编译合约:

bash

docker run --rm -it -v $(pwd):/app solidity-dev npx hardhat compile

运行测试:

bash

docker run --rm -it -v $(pwd):/app solidity-dev npx hardhat test

启动本地开发节点:

bash

docker run --rm -it -p 8545:8545 -v $(pwd):/app solidity-dev npx hardhat node

4. 使用 Docker Compose 管理开发环境

为了更方便地管理开发环境,可以使用 Docker Compose。

创建 docker-compose.yml 文件

yaml

version: '3.8'

services:
  # Solidity 开发环境
  solidity-dev:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/app
    ports:
      - "8545:8545"  # Hardhat/Ganache 端口
    networks:
      - blockchain-network
    command: bash -c "npm install && npx hardhat node"
    environment:
      - NODE_ENV=development

  # Ganache 本地区块链
  ganache:
    image: trufflesuite/ganache:latest
    ports:
      - "8545:8545"
    networks:
      - blockchain-network
    environment:
      - MNEMONIC="test test test test test test test test test test test junk"
      - CHAINID=1337
    volumes:
      - ganache-data:/data

  # IPFS 节点(可选)
  ipfs:
    image: ipfs/go-ipfs:latest
    ports:
      - "5001:5001"  # API
      - "8080:8080"  # Gateway
    volumes:
      - ipfs-data:/data/ipfs
    networks:
      - blockchain-network

  # Block Explorer(可选)
  blockscout:
    image: blockscout/blockscout:latest
    depends_on:
      - ganache
    ports:
      - "4000:4000"
    networks:
      - blockchain-network
    environment:
      - ETHEREUM_JSONRPC_HTTP_URL=http://ganache:8545
      - DATABASE_URL=postgresql://postgres:@postgres:5432/blockscout
      - ECTO_USE_SSL=false

networks:
  blockchain-network:
    driver: bridge

volumes:
  ganache-data:
  ipfs-data:

使用 Docker Compose 启动环境

启动全部服务:

bash

docker-compose up

只启动 Solidity 开发环境:

bash

docker-compose up solidity-dev

后台运行:

bash

docker-compose up -d

查看日志:

bash

docker-compose logs -f

停止所有服务:

bash

docker-compose down

5. 开发工作流与最佳实践

项目结构设置

创建一个新的 Solidity 项目:

bash

# 创建项目目录
mkdir my-solidity-project && cd my-solidity-project

# 创建基本文件结构
mkdir -p contracts scripts test

# 创建 package.json
cat > package.json << EOL
{
  "name": "my-solidity-project",
  "version": "1.0.0",
  "description": "Solidity project with Docker",
  "main": "index.js",
  "scripts": {
    "compile": "hardhat compile",
    "test": "hardhat test",
    "node": "hardhat node",
    "deploy": "hardhat run scripts/deploy.js --network localhost"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.0",
    "@nomiclabs/hardhat-waffle": "^2.0.0",
    "chai": "^4.3.4",
    "ethereum-waffle": "^3.4.0",
    "ethers": "^5.6.0",
    "hardhat": "^2.9.0"
  },
  "dependencies": {
    "@openzeppelin/contracts": "^4.5.0"
  }
}
EOL

# 创建 hardhat.config.js
cat > hardhat.config.js << EOL
require("@nomiclabs/hardhat-waffle");

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.17",
  networks: {
    hardhat: {
      chainId: 1337
    },
    localhost: {
      url: "http://127.0.0.1:8545"
    }
  }
};
EOL

# 创建示例合约
cat > contracts/SimpleStorage.sol << EOL
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract SimpleStorage {
    uint256 private value;

    event ValueChanged(uint256 newValue);

    function setValue(uint256 _value) public {
        value = _value;
        emit ValueChanged(_value);
    }

    function getValue() public view returns (uint256) {
        return value;
    }
}
EOL

# 创建部署脚本
cat > scripts/deploy.js << EOL
const hre = require("hardhat");

async function main() {
  const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage");
  const simpleStorage = await SimpleStorage.deploy();

  await simpleStorage.deployed();

  console.log("SimpleStorage deployed to:", simpleStorage.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
EOL

# 创建测试文件
cat > test/SimpleStorage.test.js << EOL
const { expect } = require("chai");

describe("SimpleStorage", function () {
  let SimpleStorage;
  let simpleStorage;
  let owner;
  let addr1;

  beforeEach(async function () {
    SimpleStorage = await ethers.getContractFactory("SimpleStorage");
    [owner, addr1] = await ethers.getSigners();
    simpleStorage = await SimpleStorage.deploy();
    await simpleStorage.deployed();
  });

  it("Should return the initial value", async function () {
    expect(await simpleStorage.getValue()).to.equal(0);
  });

  it("Should set the value", async function () {
    await simpleStorage.setValue(42);
    expect(await simpleStorage.getValue()).to.equal(42);
  });

  it("Should emit an event when value changes", async function () {
    await expect(simpleStorage.setValue(42))
      .to.emit(simpleStorage, "ValueChanged")
      .withArgs(42);
  });
});
EOL

# 创建 .dockerignore
cat > .dockerignore << EOL
node_modules
.git
.gitignore
EOL

# 创建 Docker Compose 配置
cat > docker-compose.yml << EOL
version: '3.8'

services:
  solidity-dev:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/app
    ports:
      - "8545:8545"
    environment:
      - NODE_ENV=development
    command: bash -c "npm install && npm run node"
EOL

# 创建 Dockerfile
cat > Dockerfile << EOL
FROM node:16-bullseye-slim

WORKDIR /app

RUN apt-get update && apt-get install -y \\
    git \\
    python3 \\
    make \\
    gcc \\
    g++ \\
    && rm -rf /var/lib/apt/lists/*

COPY package.json package-lock.json* ./
RUN npm install

COPY . .

CMD ["bash"]
EOL

开发工作流程

使用 Docker 的 Solidity 开发工作流程:

  1. 初始化项目

    bash

    docker-compose run --rm solidity-dev npm init -y
    docker-compose run --rm solidity-dev npx hardhat init
    
  2. 编写智能合约
    contracts/ 目录中创建和编辑 Solidity 文件

  3. 编译合约

    bash

    docker-compose run --rm solidity-dev npx hardhat compile
    
  4. 编写测试
    test/ 目录中创建和编辑测试文件

  5. 运行测试

    bash

    docker-compose run --rm solidity-dev npx hardhat test
    
  6. 启动本地区块链

    bash

    docker-compose up ganache
    

    或者

    bash

    docker-compose run --rm -p 8545:8545 solidity-dev npx hardhat node
    
  7. 部署合约

    bash

    docker-compose run --rm solidity-dev npx hardhat run scripts/deploy.js --network localhost
    
  8. 与合约交互

    bash

    docker-compose run --rm solidity-dev npx hardhat console --network localhost
    

6. 高级配置与优化

多版本 Solidity 支持

修改 Dockerfile 以支持多个 Solidity 版本:

dockerfile

FROM node:16-bullseye-slim

WORKDIR /app

RUN apt-get update && apt-get install -y \
    git \
    curl \
    python3 \
    python3-pip \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# 安装 solc-select 以支持多个 Solidity 版本
RUN pip3 install solc-select

# 安装多个 Solidity 版本
RUN solc-select install 0.8.17 0.8.10 0.7.6 0.6.12 0.5.17

# 设置默认版本
RUN solc-select use 0.8.17

# 安装开发工具
RUN npm install -g hardhat truffle ganache @openzeppelin/contracts-ethereum-package

COPY package.json* package-lock.json* /tmp/
RUN cd /tmp && npm install && mkdir -p /app/node_modules && cp -R /tmp/node_modules/. /app/node_modules/

CMD ["bash"]

VSCode 远程开发集成

为了更好地与 VSCode 集成,可以创建 .devcontainer 配置:

创建 .devcontainer/devcontainer.json

json

{
  "name": "Solidity Development",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "solidity-dev",
  "workspaceFolder": "/app",
  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash",
    "solidity.compileUsingRemoteVersion": "0.8.17",
    "solidity.defaultCompiler": "remote"
  },
  "extensions": [
    "juanblanco.solidity",
    "tintinweb.solidity-visual-auditor",
    "ms-vscode.vscode-typescript-tslint-plugin",
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode"
  ],
  "forwardPorts": [8545],
  "remoteUser": "node"
}

性能优化

优化 Docker 容器性能:

  1. 使用多阶段构建

    dockerfile

    # 构建阶段
    FROM node:16-bullseye-slim AS builder
    
    WORKDIR /build
    
    COPY package.json package-lock.json* ./
    RUN npm ci
    
    # 运行阶段
    FROM node:16-bullseye-slim
    
    WORKDIR /app
    
    COPY --from=builder /build/node_modules ./node_modules
    COPY . .
    
    CMD ["bash"]
    
  2. 构建时缓存管理

    bash

    docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t solidity-dev:latest .
    
  3. 使用卷挂载替代复制:在 docker-compose.yml 中使用卷挂载:

    yaml

    volumes:
      - .:/app
      - node_modules:/app/node_modules
    

7. 常见问题与解决方案

权限问题

问题: 在 Linux 上,Docker 创建的文件可能属于 root 用户,导致权限问题。

解决方案:

  1. 在 Dockerfile 中添加用户:

    dockerfile

    # 创建与主机用户相同 UID 的用户
    ARG USER_ID=1000
    ARG GROUP_ID=1000
    
    RUN groupadd -g ${GROUP_ID} devuser && \
        useradd -u ${USER_ID} -g devuser -s /bin/bash -m devuser
    
    USER devuser
    
  2. 构建时传递当前用户 ID:

    bash

    docker build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) -t solidity-dev .
    

网络连接问题

问题: 容器之间或容器与宿主机之间的网络连接问题。

解决方案:

  1. 确保在 docker-compose.yml 中正确配置网络:

    yaml

    networks:
      blockchain-network:
        driver: bridge
    
  2. 在容器内使用服务名而不是 localhost:

    javascript

    // 错误的方式
    const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
    
    // 正确的方式
    const provider = new ethers.providers.JsonRpcProvider("http://ganache:8545");
    

磁盘空间问题

问题: Docker 镜像和容器可能占用大量磁盘空间。

解决方案:

  1. 定期清理未使用的容器和镜像:

    bash

    # 删除所有已停止的容器
    docker container prune
    
    # 删除所有未使用的镜像
    docker image prune
    
    # 删除所有未使用的资源
    docker system prune
    
  2. 在 Dockerfile 中合并 RUN 命令减少层数:

    dockerfile

    # 不推荐:多个 RUN 命令
    RUN apt-get update
    RUN apt-get install -y git
    RUN apt-get install -y curl
    
    # 推荐:合并 RUN 命令
    RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
    

依赖安装问题

问题: npm 依赖安装失败或冲突。

解决方案:

  1. 使用 npm ci 代替 npm install

    dockerfile

    COPY package.json package-lock.json* ./
    RUN npm ci
    
  2. 清除 npm 缓存:

    dockerfile

    RUN npm cache clean --force
    

8. 附录:有用的 Docker 命令

基本 Docker 命令

bash

# 查看运行中的容器
docker ps

# 查看所有容器(包括已停止的)
docker ps -a

# 停止容器
docker stop <container_id>

# 移除容器
docker rm <container_id>

# 查看镜像
docker images

# 移除镜像
docker rmi <image_id>

# 查看容器日志
docker logs <container_id>

# 实时查看日志
docker logs -f <container_id>

# 进入正在运行的容器
docker exec -it <container_id> bash

# 查看 Docker 使用的磁盘空间
docker system df

# 查看容器资源使用情况
docker stats

Docker Compose 命令

bash

# 启动所有服务
docker-compose up

# 后台启动所有服务
docker-compose up -d

# 停止所有服务
docker-compose down

# 查看服务日志
docker-compose logs

# 实时查看日志
docker-compose logs -f

# 重新构建服务
docker-compose build

# 构建并启动服务
docker-compose up --build

# 运行特定服务的命令
docker-compose run --rm <service_name> <command>

# 只启动某个服务
docker-compose up <service_name>

# 查看服务状态
docker-compose ps

结论

使用 Docker 进行 Solidity 开发提供了一个一致、可移植的环境,使开发团队能够在任何平台上获得相同的开发体验。通过遵循本指南中的步骤和最佳实践,您可以快速设置和优化您的 Solidity 开发工作流程,专注于编写和测试智能合约,而不是解决环境配置问题。

无论您是个人开发者还是团队的一部分,Docker 都能显著提高您的开发效率并简化协作过程。随着区块链技术和智能合约开发的不断发展,拥有一个可靠的开发环境变得越来越重要,Docker 为此提供了理想的解决方案。