本文是一份完整的操作指南,介绍了如何仅使用Docker Compose在Traefik的配合下,通过HTTPS、自定义域名以及预签名的上传/下载URL来运行MinIO。

在实际生产环境中,您仍然会继续使用由第三方提供的S3、Cloudflare R2或Hetzner对象存储服务;而所有的测试环境中的文件上传、下载操作,以及预签名的URL访问,都可以免费在您自己的服务器上完成。

目录

1. 为什么要在测试环境中自行托管对象存储服务?

如果您的应用程序需要处理各种文件,比如PDF文档、个人资料图片、应用记录或录音文件,那么QA团队在进行每次测试时上传的这些文件都会产生实际的费用——这些费用会计入AWS S3、Cloudflare R2或Hetzner对象存储服务的账单中。虽然每份文件的花费并不高,但在测试环境中,您往往会遇到以下情况:

  • 需要运行自动化端到端测试,而这些测试会生成成千上万的虚拟文件;

  • 每晚都需要重置数据库,但这会导致一些文件被遗留下来;

  • 开发人员可能会尝试使用有缺陷的代码进行测试,从而导致相同的文件被反复上传;

  • 还会积累大量长时间未被删除的测试数据。

在生产环境中,这些费用是合理的——因为托管服务可以提供数据复制、高可用性等保障;但在测试环境中,这些费用纯粹是一种浪费。

MinIO是一款免费、开源的、兼容S3的对象存储服务。它使用与S3相同的API、SDK以及预签名URL,也支持mc/aws s3命令行工具——不过它是运行在用户自己的VPS上的,每GB的存储费用仅为0美元。只需将测试环境的应用程序指向MinIO,将生产环境的应用程序指向S3或R2,那么唯一需要更改的其实就是环境变量而已。

这样做的好处是:两种环境中的代码路径完全相同;在测试环境中不会产生任何存储费用;而且如果云服务提供商出现故障,这种架构也能提供可靠的备用方案。

2. 架构:生产环境与测试环境

在实际应用中,人们通常不希望开发环境或测试环境直接将数据写入生产环境的存储系统中。

一种常见且成本效益较高的配置方式是:

  • 生产环境:使用托管式的云对象存储服务

  • 测试/开发环境:使用自托管的、兼容S3的存储系统

这种配置的优点在于,应用程序的代码完全不需要进行任何修改。只要这两种存储系统都支持S3协议,那么相同的SDK和上传逻辑就可以在所有环境中正常使用,唯一不同的就是环境变量而已。

高级架构

高级架构图:同一个Next.js应用程序在生产环境中通过S3协议将文件上传到Cloudflare R2,而在测试环境中则通过MinIO进行上传。

上图说明了同一个应用程序如何根据不同的部署环境与不同的存储服务进行交互。在生产环境中,上传的数据会存储在AWS S3、Cloudflare R2或Hetzner Object Storage等托管式对象存储服务中;而这些服务会负责处理数据的持久性、可扩展性、备份以及基础设施管理等工作。而在测试环境中,上传数据会被发送到运行在VPS上的MinIO实例中,由于MinIO实现了S3协议,因此它的行为与生产环境中的存储系统类似,但成本却要低得多。

因为这两种存储系统都兼容S3协议,所以应用程序在任何环境中都可以使用相同的上传逻辑。唯一需要调整的就是通过环境变量来设置不同的配置参数而已。

为什么这种架构很有用

这种配置方式能够带来以下好处:

  • 低成本的测试环境

  • 类似生产环境的测试环境

  • 不会受到任何存储服务提供商的限制

  • 可以随时更换存储服务提供商,而无需修改应用程序代码

由于两种环境都使用S3协议,因此应用程序的上传逻辑在所有环境中都是完全相同的。

示例环境变量

您的应用程序仅会读取以下这些环境变量:

S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET=

只要更换这些变量的值,同一个应用程序就会将文件上传到不同的后端服务器上。

生产环境存储示例

在生产环境中,您通常会使用诸如以下这样的对象存储服务:

  • AWS S3

  • Cloudflare R2

  • Hetzner Object Storage

示例:

S3_ENDPOINT=https://.r2.cloudflarestorage.com

使用这些服务的优势在于:它们具有高度的可扩展性、在全球范围内均可使用、数据具有持久性,并且还会提供自动备份功能,同时用户也无需负责基础设施的维护工作。

测试环境示例

对于测试环境来说,使用一个轻量级的、自我托管的MinIO容器通常就足够了。

Next.js应用
     ↓
MinIO容器(运行在VPS上的Docker中)

示例域名:

服务 域名 内部端口
MinIO S3 API minio-staging.domain.com 9000
MinIO Web控制台 minio-console-staging.domain.com 9001

这样,您就可以:

  • 安全地测试文件上传功能

  • 避免产生生产环境中的存储成本

  • 在本地模拟生产环境中的运行情况

3. 先决条件

您需要准备以下内容:

  • 一台Linux版本的VPS服务器(Hetzner、DigitalOcean、Contabo、OVH等,只要具备公共IP地址即可)。

  • 两条指向该IP地址的A记录(我们接下来会进行配置)。

  • Docker以及Docker Compose v2版本。

  • 在前面部署Traefik v2版本,并配置Let’s Encrypt;任何反向代理服务器都可以使用——下面列出的设置是针对Traefik而言的)。

  • 在防火墙中打开80443端口,以便让Let’s Encrypt和HTTPS协议能够正常工作。

  • 至少需要10GB的空闲磁盘空间,用于存放MinIO数据。

如果您的系统中还没有安装Docker,请执行以下命令:

curl -fsSL https://get.docker.com | sh
sudo apt-get install -y docker-compose-plugin
docker --version && docker compose version

4. 第一步——DNS配置:将您的域名指向测试服务器

在您的DNS服务提供商处(例如Cloudflare、Route 53、Namecheap等),创建两条A记录,将这些记录的目标地址设置为您测试服务器的公共IP地址:

minio-staging.domain.com           A    203.0.113.45
minio-console-staging.domain.com   A    203.0.113.45

如果你们使用了Cloudflare,那么请将minio-staging.*的代理设置调整为仅DNS模式(即使用灰色的云图标标识)。Cloudflare的免费计划允许上传的最大文件大小为100MB,而且我们也不希望它删除S3请求中的签名头信息。如果你们希望在控制台子域名前添加WAF防护,那么也可以继续使用代理设置。

稍等片刻,然后验证一下设置是否正确:

dig +short minio-staging.domain.com
# 应该显示203.0.113.45这个地址

5. 第二步——使用Docker Compose运行MinIO

请将以下配置添加到你们的 stagingcompose 文件中(docker-compose.staging.yml)。MinIO实际上只是一个容器,它的存储空间会被挂载为Docker卷,因此数据在系统升级时也不会丢失。

# docker-compose.staging.yml
networks:
  proxy:
    external: true
    name: proxy
  internal:
    name: internal

volumes:
  minio-data:

services:
  minio:
    image: minio/minio:latest
    container_name: minio-staging
    restart: unless-stopped
    environment:
      - MINIO_ROOT_USER=${MINIO_ROOT_USER:-admin}
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-change-me-please}
      # 指定MinIO使用哪个公共域名来生成签名URL
      - MINIO_SERVER_URL=https://minio-staging.domain.com
      - MINIO_BROWSER_REDIRECT_URL=https://minio-console-staging.domain.com
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data
    networks:
      - proxy
      - internal
    ports:
      - "9000:9000"  # S3 API端口
      - "9001:9001"  # Web控制台端口
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

有两点需要注意:

  • MINIO_SERVER_URL这个配置非常关键。如果没有设置这个参数,MinIO会使用它内部的域名来生成签名URL(http://minio:9000),而当浏览器访问这些URL时,验证会失败。因此,请务必将其设置为客户端实际使用的HTTPS地址。

  • MINIO_BROWSER_REDIRECT_URL这个配置用于处理Web控制台的登录重定向、OIDC回调等功能。

现在来启动这些服务:

docker compose -f docker-compose.staging.yml up -d minio
docker compose -f docker-compose.staging.yml logs -f minio

你应该会看到类似“API地址:http://……”和“控制台地址:http://……”这样的输出信息。

6. 第三步——使用Traefik通过HTTPS暴露MinIO服务

我们不会直接将9000/9001端口暴露给外部网络——这个工作是由Traefik来完成的,它会使用Let’s Encrypt提供的免费证书来处理TLS加密流程。

请为minio服务添加以下标签:

    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"

      # ---- S3 API端口(9000) ----
      - "traefik.http.routers.minio-staging.rule=Host(`minio-staging.domain.com`)"
      - "traefik.http.routers.minio-staging.entrypoints=websecure"
      - "traefik.http.routers.minio-staging.tls_certresolver=letsencrypt"
      - "traefik.http.routers.minio-staging.service=minio-staging"
      - "traefik.http.services.minio-staging.loadbalancer.server.port=9000"

      # ---- Web控制台端口(9001) ----
      - "traefik.http.routers.minio-console-staging.rule=Host(`minio-console-staging.domain.com`)"
      - "traefik.http.routers.minio-console-staging.entrypoints=websecure"
      - "traefik.http.routers.minio-console-staging.tls_certResolver=letsencrypt"
      - "traefik.http.routers.minio-console-staging.service=minio-console-staging"
      - "traefik.http.services.minio-console-staging.loadbalancer.server.port=9001"

您还需要为端口`:443`配置一个`entrypoint`,以及一个名为`letsencrypt`的`certificatesResolver`。以下是最低限度的Traefik配置文件(`traefik.staging.yml`):

api:
  dashboard: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      httpChallenge:
        entryPoint: web
      email: admin@domain.com
      storage: /etc/traefik/acme.json

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy

重新启动系统,然后等待证书的生成:

docker compose -f docker-compose.staging.yml up -d
docker compose -f docker-compose.staging.yml logs -f traefik | grep -i acme

在您的笔记本电脑上进行测试:

curl -I https://minio-staging.domain.com/minio/health/live
# HTTP/2 200

现在您可以使用`admin`或`change-me-please`账号登录到`https://minio-console-staging.domain.com`这个Web控制台

关于上传文件大小的重要设置:如果您在Traefik之前使用了Cloudflare或NGINX,就需要增加请求体的最大传输大小。Traefik本身没有默认的限制,但Cloudflare的免费计划不允许超过100 MB的文件上传。对于自己托管的边缘代理服务器,可以在配置文件中设置`client_max_body_size 0;`(针对NGINX)来调整这个限制。

7. 第四步——创建存储桶和访问密钥

任何能够与S3交互的工具都可以用来操作MinIO。最简单的工具就是`mc`,它是MinIO官方提供的客户端程序,同样包含在部署使用的镜像中。

7.1 将`mc`连接到您的服务器

docker exec -it minio-staging \
  mc alias set local http://localhost:9000 admin change-me-please

7.2 创建存储桶

docker exec -it minio-staging mc mb local/domain-files-staging

7.3 选择存储桶访问策略

您有三种可选的策略,具体选择哪种取决于您要存储的数据类型:

策略 适用场景
private(默认值) 适用于敏感数据,如学生成绩单、合同文件或内部文档。只能通过预签名的URL进行访问。
download 允许公众读取,但不支持列表显示。适合用于头像等CDN类型的资源。
public 任何人都可以阅读和列出文件内容。仅适用于完全公开的内容。

请选择合适的策略进行设置:

# 推荐使用private策略来保护文档
docker exec -it minio-staging \
  mc anonymous set none local/domain-files-staging

# 或者仅针对静态资源使用public read策略:
docker exec -it minio-staging \
  mc anonymous set download local/domain-files-staging

7.4 创建专用的应用用户(切勿使用root密钥!)

admin账户具有删除所有数据的权限。因此,应为你的应用程序创建一个权限较低的专用用户:

docker exec -it minio-staging mc admin user add local \
  domain-app a-long-random-secret-key

# 通过JSON文件为该用户配置仅针对某个存储桶的读写权限:
cat > /tmp/policy.json <<>'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:*"],
      "Resource": [
        "arn:aws:s3:::domain-files-staging",
        "arn:aws:s3:::domain-files-staging/*"
      ]
    }
  ]
}
EOF

docker cp /tmp/policy.json minio-staging:/tmp/policy.json
docker exec -it minio-staging \
  mc admin policy create local domain-rw /tmp/policy.json
docker exec -it minio-staging \
  mc admin policy attach local domain-rw --user domain-app

请记住这两个值——它们就是你的S3_ACCESS_KEYS3_SECRET_KEY

8. 第五步:配置应用程序仅在测试环境中使用MinIO

实现“在测试环境使用MinIO,在生产环境使用真实的S3服务”的关键在于,在代码中使用相同的S3客户端,只需更改环境变量即可。

你的staging.env文件内容如下(该文件由你的测试环境配置文件加载):

# ---- 测试环境:自托管的MinIO ----
STORAGE_ENABLED=true
S3_ENDPOINT=https://minio-staging.domain.com
S3_PUBLIC_endpoint=https://minio-staging.domain.com
S3_bucket=domain-files-staging
S3_ACCESS_KEY=domain-app
S3_SECRET_KEY=a-long-random-secret-key
S3_REGION=us-east-1
S3_force_PATH_STYLE=true

而你的production.env文件内容如下:

# ---- 生产环境:Cloudflare R2 ----
STORAGE_ENABLED=true
S3_ENDPOINT>=https://.r2.cloudflarestorage.com
S3_PUBLIC_endpoint=https://files.domain.com
S3_bucket=domain-files
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_REGION=auto
S3_force_PATH_style=true

S3_FORCE_PATH_STYLE=true这个设置对于MinIO以及R2/Hetzner服务来说都是至关重要的。如果不启用这个选项,SDK会尝试使用https://bucket.minio-staging.domain.com这个地址进行连接,但这样的地址是无法解析成功的。

现在,在你的应用程序代码中(以使用AWS SDK v3的Node.js示例为例):

// src/lib/s3.js
import { S3Client } from "@aws-sdk/client-s3";

export const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,
  region: process.env.S3_REGION,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey: process.env.S3_SECRET_KEY,
  },
  forcePathStyle: process.env.S3_force_PATH_STYLE === "true",
});

export const BUCKET = process.env.S3_bucket;
export const PUBLIC_ENDPOINT = process.env.S3_PUBLIC_endpoint;

现在,同一个s3实例可以在测试环境中与MinIO通信,在生产环境中与R2/Hetzner服务通信,而无需对代码进行任何修改。

9. 第六步——上传文件(三种方法)

9.1 从服务器上传文件(适用于可信任的后端服务)

import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3, BUCKET } from "./lib/s3.js";
import { readFile } from "node:fs/promises";

export async function uploadDocument(localPath, key, contentType) {
  const Body = await readFile(localPath);
  await s3.send(new PutObjectCommand({
    Bucket: Bucket,
    Key: key,
    Body,
    ContentType: contentType,
    // 可选:对象元数据,有助于审计
    Metadata: { uploadedBy: "system", env: process.env.NODE_ENV },
  }));
  return key;
}

9.2 使用mc CLI上传文件(适用于一次性上传或数据迁移操作)

mc alias set staging https://minio-staging.domain.com domain-app a-long-random-secret-key
mc cp ./report.pdf staging/domain-files-staging/reports/2026/report.pdf
mc ls staging/domain-files-staging --recursive

9.3 通过预签名的PUT URL直接从浏览器上传文件

推荐的用户上传方式是:文件直接从浏览器传输到MinIO,过程中完全不经过您的API服务器。

我们接下来会详细介绍这一过程。

10. 第七步——生成预签名URL(用于PUT和GET操作)

预签名URL是一种普通的HTTPS链接,其查询字符串中包含一个有时间限制的签名。拥有该URL的人可以在接下来的N分钟内执行该URL所对应的操作(例如上传文件或获取文件内容),除此之外无法做其他任何事情。

正是这一机制使得“用户直接将文件上传到存储系统”这一操作变得安全可靠。

10.1 预签名PUT请求(用于文件上传)

// src/lib/presign.js
import { PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3, BUCKET } from "./s3.js";
import { randomUUID } from "node:crypto";

export async function presignUpload({ filename, contentType, userId }) {
  const key = `users/\({userId}/\){randomUUID()}-${filename}`;
  const cmd = new PutObjectCommand({
    Bucket: Bucket,
    Key: key,
    ContentType: contentType,
  });
  const uploadUrl = await getSignedUrl(s3, cmd, { expiresIn: 60 * 5 }); // 有效期为5分钟
  return { uploadUrl, key };
}

您可以将这段代码集成到您的API中:

// POST /api/uploads/presign
app.post("/api/uploads/presign", requireAuth, async (req, res) => {
  const { filename, contentType } = req.body;
  const result = await presignUpload({
    filename,
    contentType,
    userId: req.user.id,
  });
  res.json(result); // 返回{ uploadUrl, key }
});

浏览器会直接将文件上传到MinIO:

// 在前端代码中
async function uploadFile(file) {
  const { uploadUrl, key } = await fetch("/api/uploads/presign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ filename: file.name, contentType: file.type }),
  }).then(r => r.json());

  await fetch(uploadUrl, {
    method: "PUT",
    headers: { "Content-Type": file.type },
    body: file,
  });

  // 将`key`保存到数据库中,以便日后检索
  await fetch("/api/documents", {
    method: "POST",
    body: JSON.stringify({ key, originalName: file.name }),
  });
}

在使用PUT请求时,你设置的Content-Type必须与之前用于生成预签名URL的Content-Type完全一致,否则MinIO会以SignatureDoesNotMatch为由拒绝该请求。这一点在第一次使用时就需要特别注意。

10.2 预签名GET请求(用于文件下载)

原理相同,只是使用了GetObjectCommand方法:

export async function presignDownload(key, expiresIn = 60 * 10) {
  const cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key });
  return getSignedUrl(s3, cmd, { Expires: expireTime });
}

一个典型的“查看文档”接口示例:

app.get("/api/documents/:id/url", requireAuth, async (req, res) => {
  const doc = await dbdocuments.findById(req.params.id);
  if (!doc || !canUserSee(req.user, doc)) return res.status(403);
  const url = await presignDownload(doc.key, 600);
  res.json({ url });
});

前端只需打开这个URL,文件就会直接从MinIO传输到用户手中。

10.3 为什么预签名URL比“通过API代理”更高效

通过API代理 预签名URL
经过你应用程序处理的字节数 所有数据都会经过你的应用程序 零字节
API所需的CPU资源/内存消耗 较高 无需任何资源
吞吐量限制 受你的API限制 由MinIO的网络接口决定
身份验证流程 由你的代码负责处理 在生成预签名URL之前,也需要进行身份验证

11. 第8步——获取文档的公共访问地址

有时你需要一个永久性的、无需身份验证即可访问的URL,比如用于显示用户的公开头像。

如果桶策略允许匿名访问(例如使用mc anonymous set download …命令),那么公共访问地址的格式如下:

https://minio-staging.domain.com/<bucket>>/<key>

因此,users/42/avatar.png对应的公共访问地址就是:

https://minio-staging.domain.com/domain-files-staging/users/42/avatar.png

在代码中实现的方式如下:

export function publicUrl(key) {
  return `\({process.env.S3_PUBLIC_ENDPOINT}/\){BUCKET}/${key}`;
}

对于私有存储桶(即大多数文档),绝对不要使用公共URL——务必通过presignDownload(key)函数来处理数据,这样你就可以在每个请求时重新检查授权信息,并确保链接在过期后自动失效。

12. 第九步——限制CORS访问、管理数据生命周期并加强安全性

12.1 允许前端请求的来源地址(CORS配置)

浏览器上传数据时,存储桶需要遵循CORS规则。请通过mc命令添加以下JSON配置文件:

cat > /tmp/cors.json <<>'EOF'
{
  "CORSRules": [
    {
      "AllowedOrigins": [
        "https://crm-staging.domain.com",
        "http://localhost:3000"
      ],
      "AllowedMethods": ["GET", "PUT", "POST", "HEAD"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3000
    }
  ]
}
EOF

docker cp /tmp/cors.json minio-staging:/tmp/cors.json
docker exec -it minio-staging \
  mc cors set local/domain-files-staging /tmp/cors.json

12.2 自动删除过期的测试文件(管理数据生命周期)

staging目录中会积累大量冗余文件。请指示MinIO自动删除超过30天的旧文件:

docker exec -it minio-staging \
  mc ilm rule add --expire-days 30 local/domain-files-staging

12.3 对静态数据进行加密存储

docker exec -it minio-staging \
  mc encrypt set sse-s3 local/domain-files-staging

12.4 严格的规则

  • 绝对不要MINIO_ROOT_USER=adminMINIO_ROOT_PASSWORD(admin123这些凭据发送到可被互联网访问的服务器上。请生成强密码并将其存储在秘密管理工具中。

  • root账户仅应由mc admin命令使用,应用程序绝不能使用该账户。应用程序应该使用具有特定权限的IAM用户(参见第7.4步)。

  • 如果控制台子域名确实需要对外公开访问,那么请通过IP地址白名单或Traefik中间件来实现基本身份验证。

  • 应用程序的访问密钥至少每90天更新一次。

13. 第十步——备份与监控

13.1 备份数据:每周将数据复制到便宜的冷存储桶中

可以设置一个定时任务,使用mc mirror命令将数据定期备份到Backblaze B2、R2或其他廉价的S3存储服务上:

mc alias set b2 https://s3.us-east-005.backblazeb2.com \(B2_KEY \)B2_SECRET
mc mirror --overwrite --remove \
  staging/domain-files-staging \
  b2/domain-staging-backup

即使按每TB每月6美元的费用计算,对于用于测试环境的存储空间来说,这也几乎算是免费的。

13.2 使用Prometheus进行监控

MinIO默认会在/minio/v2/metrics/cluster路径上提供Prometheus指标数据。你可以使用相应的工具来采集这些数据并进行分析。

scrape_configs:
  - job_name: minio
    metrics_path: /minio/v2/metrics/cluster
    scheme: https
    staticconfigs:
      - targets: ["minio-staging.domain.com"]

如果你们使用了Grafana,可以导入编号为13502的仪表板,以便快速查看各项指标(容量、请求率、延迟、错误数量等)。

14. 故障排查速查表

>

症状 可能的原因 解决方法
在预签名PUT请求中出现SignatureDoesNotMatch错误 浏览器发送的Content-Type与预先签名的值不同 在发送PUT请求时,确保使用完全相同的Content-Type头信息
预签名URL在本地可以正常使用,但在浏览器中无法访问 未设置MINIO_SERVER_URL,因此URL是针对minio:9000进行签名的 MINIO_SERVER_URL设置为https://minio-staging.domain.com,然后重新启动服务
通过Cloudflare后出现403 SignatureDoesNotMatch错误 Cloudflare会删除或修改请求头信息 将DNS记录设置为DNS-only模式
出现NoSuchBucket错误 应用程序指向了错误的端点或存储桶 重新检查环境变量中的S3_ENDPOINTS3_bucket
浏览器尝试进行CORS预检请求时失败 存储桶未配置CORS规则 按照§12.1节的内容应用CORS相关设置
小文件上传可以成功,但超过100MB的文件无法上传 这是由于Cloudflare免费版的文件传输限制所致 使用Cloudflare付费版,或者直接跳过CF代理
应用程序中出现x509: certificate signed by unknown authority错误 应用程序容器不信任Let’s Encrypt提供的证书 更新CA证书包(执行apt install ca-certificates命令),或在Docker网络内部使用HTTP协议
Web控制台重定向到http://minio:9001/login地址 未设置MINIO_BROWSER_REDIRECT_URL 将其设置为https://minio-console-staging.domain.com

一些有用的诊断工具:

# 检查MinIO的服务状态
curl -I https://minio-staging.domain.com/minio/health/live

# 列出某个存储桶中的所有对象
docker exec -it minio-staging mc ls --recursive local/domain-files-staging

# 查看MinIO的日志记录
docker compose -f docker-compose.staging.yml logs -f minio

# 解码预签名URL,查看其对应的请求内容
echo "<paste url>" | tr '&' '\n'

15. 总结

目前你们已经实现了以下功能:

  • 在一个独立的测试服务器上运行了一个免费的、兼容S3标准的对象存储系统。
  • 通过Traefik和Let’s Encrypt,成功实现了基于真实域名的HTTPS连接(地址为https://minio-staging.domain.com)。
  • 应用了最小权限原则,确保root密钥得到妥善保护。
  • 测试环境和生产环境使用完全相同的代码路径。在环境变量中只需更改4个参数,就可以在MinIO、R2、Hetzner或AWS S3之间切换。
  • 通过预签名PUT URL,用户可以直接将文件上传到存储系统,而无需经过API接口。
  • 利用预签名GET URL,私密文档可以被设置为过期时间较短且需要授权才能访问。
  • 配置了生命周期规则,能够自动删除过期的测试文件。
  • 还提供了可选的每周备份功能,数据会复制到冷备份存储桶中。

生产环境中的数据仍然存储在那些需要遵守服务水平协议的托管存储系统中。而现在,进行测试数据存储所花费的费用仅为每上传1吉字节数据只需0美元——这样你们终于可以不再反复要求质量保障团队“在测试完成后删除这些测试文件”了。

进一步阅读资料

如果本指南为你的团队节省了一些费用,那就把它分享给那些仍在将测试文件上传到每月需支付90美元费用的S3存储桶中的团队吧。祝好运!

Comments are closed.