Foreword
When looking for a self-hosted image hosting or private cloud storage solution, MinIO is often the first choice, but for individual developers or small-to-medium teams, Garage is a lighter, higher-performance, and more flexible alternative. It is written in Rust, has extremely low resource usage, and is fully compatible with the AWS S3 protocol.
This article will detail how to deploy a production-ready Garage object storage service using Docker Compose on the 1Panel management panel. It will record and solve common configuration pitfalls (such as CORS cross-origin, WebUI authentication, domain binding, etc.) to create a private OSS that is both secure and easy to use.
0 Preparations
- Server OS: Debian 12 (Recommended) / Ubuntu 22.04+
- Management Panel: 1Panel (with OpenResty/Nginx installed)
- Core Tools: Docker & Docker Compose
- Planned Domains:
s3.example.com—— S3 API Endpoint (for upload/management tools)img.example.com—— Public Access Domain (for embedding images in blogs/sites)admin.example.com—— Web Admin Panel (WebUI)
1 Configuration File Preparation (garage.toml)
- Create the directory
/opt/garage/configin 1Panel. - Create the file
garage.tomland fill it with the following content:
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "lmdb"
# 单节点部署必须设为 1;多节点请按需调整
replication_factor = 1
# 32字节的随机密钥 (openssl rand -hex 32 生成)
rpc_secret = "c9a4b8d7e6f5a1c2b3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6"
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = "s3.example.com" # 填你的API域名
[s3_web]
bind_addr = "[::]:3902"
root_domain = "img.example.com" # 填你的公开访问域名
index = "index.html"
[admin]
api_bind_addr = "[::]:3903"
metrics_token = "change_me_please" # metrics 抓取用的 token
admin_token = "change_me_please" # 管理员 token,WebUI 会自动读取
2 Writing the Compose File (docker-compose.yml)
Key points:
- WebUI mount configuration: The WebUI container must mount
garage.tomlto automatically readadmin_token. - Password escaping: In YAML, the
$sign in Bcrypt password hashes must be written as$$.
Create a compose stack garage-stack in 1Panel with the following content:
services:
garage:
image: dxflrs/garage:v2.2.0
container_name: garage
restart: always
network_mode: host
volumes:
- /opt/garage/config/garage.toml:/etc/garage.toml
- /opt/garage/meta:/var/lib/garage/meta
- /opt/garage/data:/var/lib/garage/data
garage-webui:
image: khairul169/garage-webui:latest
container_name: garage-webui
restart: always
network_mode: host
volumes:
# 必须挂载配置文件,否则 WebUI 无法自动鉴权
- /opt/garage/config/garage.toml:/etc/garage.toml:ro
environment:
API_BASE_URL: "http://127.0.0.1:3903"
S3_ENDPOINT_URL: "http://127.0.0.1:3900"
# 原生登录配置:用户名:Bcrypt哈希 (注意 $$ 转义)
# 生成命令: python3 -c "import bcrypt; print(bcrypt.hashpw(b'你的密码', bcrypt.gensalt()).decode())"
# 下面示例密码是 123456
AUTH_USER_PASS: 'admin:$$2b$$12$$zm7W20msXJc5fVgj.fjGR.GfGQS2M1abvQf5k.aWZTQBZGv4QexpC'
Start the stack and ensure both containers show as 'running'.
3 Initializing Node Layout
A freshly started Garage is in an 'unassigned role' state and must be initialized.
- Check Node ID:
Execute in 1Panel terminal or SSH:
docker exec -it garage /garage status
Copy the displayed Node ID, e.g., a8795c63e0c82b0b.
- Allocate Space and Apply Configuration:
# 分配 500GB 逻辑空间 (不会立即占用硬盘)
docker exec -it garage /garage layout assign -z dc1 -c 500G <YourNodeID>
# 应用更改
docker exec -it garage /garage layout apply --version 1
At this point, refresh the WebUI and the status should change to green Healthy.
4 Creating Buckets and Configuring Permissions
To use it as an image hosting, we need to create a bucket and allow public access.
1. Create a Bucket
In the WebUI, click Buckets -> Create Bucket and name it photo.
2. Bind Public Domain (Alias) & Enable Website Mode
This is the key to making images accessible directly via https://img.example.com/xxx.jpg.
# 1. 绑定域名别名 (让 Garage 知道这个域名对应 photo 桶)
docker exec -it garage /garage bucket alias set photo img.example.com
# 2. 开启网站模式 (允许匿名 GET 请求)
docker exec -it garage /garage bucket website --allow photo --index index.html
You can also set Alias and enable Website directly in the WebUI.

WebUI interface
3. Create API Key
- In the WebUI, click Keys -> Create Key (name it e.g.,
blog-key). - Immediately save the displayed Access Key ID and Secret Access Key.
- Click Permissions on the right side of the Key and check the Read/Write permission for the
photobucket.- Or authorize via command line:
docker exec -it garage /garage bucket allow --read --write --key blog-key photo
- Or authorize via command line:
5 Reverse Proxy and HTTPS Configuration (1Panel)
We need to set up three reverse proxies in the 1Panel 'Websites' section:
| Domain | Proxy Address | Purpose |
|---|---|---|
s3.example.com | http://127.0.0.1:3900 | S3 API (PicList/Lsky fill this) |
admin.example.com | http://127.0.0.1:3903 | WebUI Admin Panel |
img.example.com | http://127.0.0.1:3902 | Public Image Access (Note the port is 3902) |
Remember to apply for and enable HTTPS certificates for all domains.
Security Tip: When using Host mode, it is recommended to only allow Nginx's ports 80/443 in the 1Panel firewall or cloud provider's security group. Do not directly expose Garage's ports 3900-3903 to the outside, so that all requests must go through the Nginx reverse proxy for better security.
6 Solving CORS Cross-Origin Issues
If you embed images in blogs or other websites, the browser will block cross-origin requests. We need to force allow CORS in the Nginx configuration of img.example.com.
In the 1Panel website settings -> Configuration file, find the location / block and add the following:
location / {
# CORS 配置
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
# 其余配置
}
}
Save and reload Nginx.
7 Client Connection Configuration
Now the service is fully ready. Fill in the following configuration in the image hosting tool:
- S3 Endpoint:
https://s3.example.com - Bucket:
photo - Access Key ID: (ID obtained in step 4)
- Secret Access Key: (Secret obtained in step 4)
- Region:
garage(default) - Force Path Style: Enable (True)
- Custom Domain:
https://img.example.com