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

S3 全称 Simple Storage Service,是亚马逊 AWS 在 2006 年推出的对象存储标准服务接口规范,现在已经成为行业通用的对象存储协议,不只是 AWS 专有。
和电脑硬盘、服务器磁盘(块存储)、网盘文件夹(文件存储)不同:
- 最小存储单元是对象:文件 + 元数据(大小、上传时间、标签、权限等)打包为一个整体;
- 存储空间层级:存储桶 Bucket(顶层容器)→ 对象 Object(文件),没有传统文件夹层级,但可以用前缀模拟目录;
- 数据扁平化存放,海量文件读写性能稳定,适合存图片、备份、视频、静态资源、归档数据。
# 数据存储目录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"- 编写
/etc/systemd/system/garage.server,通过systemd将garage运行为系统服务
[Unit]Description=Garage Data StoreAfter=network-online.targetWants=network-online.target
[Service]ExecStart=/usr/local/bin/garage server --single-nodeStateDirectory=garageDynamicUser=trueProtectHome=trueNoNewPrivileges=trueLimitNOFILE=42000
[Install]WantedBy=multi-user.target- 启动服务
# 识别到server配置systemctl daemon-reload# 启动服务systemctl start garage# 检查服务状态systemctl status garage- 创建bucket和credentials
# 创建桶garage bucket create global# 创建密钥,需要保存好Secret key,后续不会展示garage key create global-key# 允许密钥访问桶garage bucket allow \ --read \ --write \ --owner \ global \ --key global-key- 通过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解决方案
-
Garage UI 需要通过docker部署,需要保证已经安装docker
-
在
/etc/garage-ui.yaml编写配置
# Garage UI Backend Configuration
# Server configurationserver: 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 Configurationgarage: 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 methodsauth: # 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 logginglogging: level: "info" # Options: debug, info, warn, error format: "text" # or "json"- 启动容器
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- 配置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; }}- 访问web后台https://garage.example.com并使用配置的管理员账号密码登录
有时想要将一些资源无需鉴权共享给他人,可以通过创建静态文件桶实现。
-
在garage-ui后台buckets菜单下新建bucket,名称为
resource.example.com,即资源域名,其中资源可通过https://resource.example.com/xxx.png的形式访问 -
进入该资源桶详情页,点击
Permissions标签页,选择一个access key,permissions中勾选read、write,并点击Grant access进行授权 -
点击
Website标签页,打开website access并保存更改 -
点击
Objects标签页,选择一个文件进行上传,成功后访问https://resource.example.com/filename.ext的形式访问验证