跳转到内容

基于Garage部署轻量级自托管S3

garage-setup-poster

S3 全称 Simple Storage Service,是亚马逊 AWS 在 2006 年推出的对象存储标准服务接口规范,现在已经成为行业通用的对象存储协议,不只是 AWS 专有。

和电脑硬盘、服务器磁盘(块存储)、网盘文件夹(文件存储)不同:

  1. 最小存储单元是对象:文件 + 元数据(大小、上传时间、标签、权限等)打包为一个整体;
  2. 存储空间层级:存储桶 Bucket(顶层容器)→ 对象 Object(文件),没有传统文件夹层级,但可以用前缀模拟目录;
  3. 数据扁平化存放,海量文件读写性能稳定,适合存图片、备份、视频、静态资源、归档数据。
  1. 官网下载页面下载最新版二进制文件并移动到/usr/local/bin目录

  2. 编写配置文件/etc/garage.toml,更多内容参考全量配置

# 数据存储目录
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
# 通过 openssl rand -hex 32 获取
rpc_secret = "xxxx"
# 客户端连接配置
[s3_api]
s3_region = "global"
api_bind_addr = "[::]:3900"
root_domain = ".garage-api.example.com"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".garage-web.example.com"
index = "index.html"
[admin]
api_bind_addr = "[::]:3903"
# 通过 openssl rand -base64 32 获取
admin_token = "xxxxx"
# 通过 openssl rand -base64 32 获取
metrics_token = "xxxxxx"
  1. 编写/etc/systemd/system/garage.server,通过systemd将garage运行为系统服务
[Unit]
Description=Garage Data Store
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/garage server --single-node
StateDirectory=garage
DynamicUser=true
ProtectHome=true
NoNewPrivileges=true
LimitNOFILE=42000
[Install]
WantedBy=multi-user.target
  1. 启动服务
Terminal window
# 识别到server配置
systemctl daemon-reload
# 启动服务
systemctl start garage
# 检查服务状态
systemctl status garage
  1. 创建bucket和credentials
Terminal window
# 创建桶
garage bucket create global
# 创建密钥,需要保存好Secret key,后续不会展示
garage key create global-key
# 允许密钥访问桶
garage bucket allow \
--read \
--write \
--owner \
global \
--key global-key
  1. 通过S3客户端验证
import { ListBucketsCommand, S3Client } from '@aws-sdk/client-s3';
async function main() {
const client = new S3Client({
endpoint: 'http://192.168.100.44:3900',
region: 'global',
// garage key create global-key 得到的密钥信息
credentials: {
accessKeyId: 'GKxxxx',
secretAccessKey:'xxxxx',
},
});
const buckets = await client.send(new ListBucketsCommand({}));
console.log(buckets);
}
main();

Garage官方并没有提供web管理页面,但是社区有开源的Garage UI解决方案

  1. Garage UI 需要通过docker部署,需要保证已经安装docker

  2. /etc/garage-ui.yaml编写配置

# Garage UI Backend Configuration
# Server configuration
server:
host: "::" # IPv6 wildcard; dual-stack behavior depends on OS/runtime socket settings
# web后台访问端口
port: 8080
environment: "production" # development, production
domain: "localhost" # Domain name for the application
protocol: "http" # Protocol for internal communication (http/https)
root_url: "https://garage.example.com" # Full external URL for OAuth2 redirects (adjust for production)
# Request size limits (in bytes)
max_body_size: 314572800 # 300MB - Maximum request body size (increase for large file uploads)
max_header_size: 1048576 # 1MB - Maximum request header size
read_buffer_size: 4096 # 4KB - Read buffer size
write_buffer_size: 4096 # 4KB - Write buffer size
# Garage S3 Configuration
garage:
endpoint: "http://localhost:3900" # Garage S3 API endpoint
region: "global" # S3 region (ensure it matches Garage S3 configuration)
# Garage Admin API configuration
admin_endpoint: "http://localhost:3903" # Garage Admin API endpoint
admin_token: "xxxx" # Admin API bearer token
# Authentication Configuration
# You can enable one or both authentication methods
auth:
# JWT Configuration
# Ed25519 private key in PEM format for JWT token signing
# If not specified, a new key will be generated on each startup (tokens won't persist across restarts)
# Generate with: openssl genpkey -algorithm ED25519 -out jwt-key.pem
# The key is a 64-byte Ed25519 private key
jwt_private_key: "" # Leave empty to auto-generate, or provide PEM-encoded Ed25519 private key
# Expose Prometheus metrics at top-level /metrics WITHOUT authentication.
# Needed for Prometheus to scrape when auth (admin/token/oidc) is enabled.
# WARNING: exposes operational cluster telemetry (no object data or secrets)
# to anyone who can reach the port. Restrict with a NetworkPolicy / firewall.
metrics_public: false # Set to true to serve /metrics unauthenticated
# Admin Authentication (username/password),用于web后台登录
admin:
enabled: true # Set to true to enable admin login
username: "admin"
password: "xxxx"
# Admin Token Authentication
# When enabled, users can log in using the Garage admin token
# Auto-enabled when no other auth method is configured (zero-config fallback)
token:
enabled: false # Set to true to explicitly enable, or leave all auth disabled for auto-enable
# OIDC Configuration
# NOTE: When OIDC is enabled, server.root_url is required for OAuth2 redirects
# The redirect URL will be automatically constructed as: {root_url}/auth/oidc/callback
oidc:
enabled: false # Set to true to enable OIDC login
provider_name: "Keycloak"
client_id: "garage-ui"
client_secret: "your-client-secret"
# OIDC scopes to request
scopes:
- openid
- email
- profile
# OIDC Provider URLs
issuer_url: "https://keycloak.example.com/realms/master"
# Token validation
skip_issuer_check: false
skip_expiry_check: false
# Attribute mappings
email_attribute: "email"
username_attribute: "preferred_username"
name_attribute: "name"
# Role-based access (optional)
role_attribute_path: "resource_access.garage-ui.roles"
# Team-based access control (optional, see access_control below).
# team_attribute_path: "groups"
# Single admin role (backward-compatible).
admin_role: "admin"
# Multiple admin roles: a user is granted admin if ANY of their roles
# matches ANY entry below. Values from admin_role and admin_roles are
# merged, so you can set either, both, or only admin_roles.
# admin_roles:
# - "garage-admins"
# - "platform-team"
# TLS configuration
tls_skip_verify: false # Only set to true for testing, not recommended for production
# Session configuration
session_max_age: 86400 # 24 hours in seconds
cookie_name: "garage_session"
cookie_secure: false # Set to true in production with HTTPS
cookie_http_only: true
cookie_same_site: "lax" # lax, strict, none
# Optional: team-based access control (see docs/access-control.md).
# Absent -> every authenticated user has full access.
# Present -> default-deny: OIDC users get only what their teams grant; users
# matching no team get 403 everywhere. admin_role users, admin
# password logins, and token logins are always full-admin.
# NOTE: this is UI-layer policy, NOT a security boundary. Anyone holding the
# Garage admin token or S3 keys bypasses it entirely.
#
# access_control:
# presets:
# bucket_readonly: [bucket.list, bucket.read, object.list, object.read]
# bucket_owner: ["preset:bucket_readonly", bucket.create, bucket.update,
# bucket.delete, object.write, object.delete]
# teams:
# - name: backend
# claim_values: ["garage-team-backend"] # matched against team_attribute_path claim
# bindings:
# - bucket_prefixes: ["backend-"]
# permissions: ["preset:bucket_owner"]
# - bucket_prefixes: ["shared-"]
# permissions: ["preset:bucket_readonly"]
# cluster_permissions: [cluster.status, cluster.health]
# CORS Configuration (for frontend)
cors:
enabled: true
allowed_origins:
- "*" # Vite default
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed_headers:
- Origin
- Content-Type
- Accept
- Authorization
allow_credentials: false
max_age: 3600
# Logging Configuration
# The application uses zerolog for structured logging
logging:
level: "info" # Options: debug, info, warn, error
format: "text" # or "json"
  1. 启动容器
Terminal window
docker run -d \
--name garage-ui \
--network host \
-v /etc/garage-ui.yaml:/app/config.yaml \
noooste/garage-ui:latest
docker logs -f garage-ui
  1. 配置nginx反向代理
server {
listen 443 ssl;
server_name garage.example.com;
charset utf-8;
client_max_body_size 200m;
ssl_certificate /etc/nginx/cert.d/example.pem;
ssl_certificate_key /etc/nginx/cert.d/example.key;
if ($http_x_from_where = "frp") {
return 404;
}
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://192.168.100.44:8080;
proxy_redirect off;
proxy_connect_timeout 300s;
}
}
  1. 访问web后台https://garage.example.com并使用配置的管理员账号密码登录

有时想要将一些资源无需鉴权共享给他人,可以通过创建静态文件桶实现。

  1. 在garage-ui后台buckets菜单下新建bucket,名称为resource.example.com,即资源域名,其中资源可通过https://resource.example.com/xxx.png的形式访问

  2. 进入该资源桶详情页,点击Permissions标签页,选择一个access key,permissions中勾选read、write,并点击Grant access进行授权

  3. 点击Website标签页,打开website access并保存更改

  4. 点击Objects标签页,选择一个文件进行上传,成功后访问https://resource.example.com/filename.ext的形式访问验证