Preface
When looking for self-hosted image hosting or private cloud storage solutions, MinIO is often the first choice. However, for individual developers or small-to-medium teams, Garage is a lighter, higher-performance, and more flexibly configurable alternative. Written in Rust, it has extremely low resource usage and is fully compatible with the AWS S3 protocol.
This article provides a detailed guide on how to deploy a production-ready Garage object storage service on the 1Panel panel using Docker Compose. It documents and addresses common configuration pitfalls (such as CORS cross-origin issues, WebUI authentication, domain binding, etc.) to build a private OSS that is both secure and easy to use.
0 Prerequisites
- 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 blog/website image references)admin.example.com— Web Admin Panel (WebUI)
1 Preparing the Configuration File (garage.toml)
- Create the directory in 1Panel:
/opt/garage/config. - Create the file
garage.tomlwith the following content:
2 Writing the Compose File (docker-compose.yml)
Key Points:
- WebUI Configuration Mounting: The WebUI container must mount
garage.tomlto automatically read theadmin_token. - Password Escaping: In YAML, the
$symbol in Bcrypt password hashes must be written as$$.
Create the compose stack garage-stack in 1Panel with the following content:
Start the compose stack and make sure both containers show as "Running".
3 Initializing the Node Layout
A freshly started Garage is in an "unassigned role" state and must be initialized.
Check the Node ID:
Run the following in the 1Panel terminal or via SSH:
Copy the displayed Node ID, e.g., a8795c63e0c82b0b.
- Assign Space and Apply Configuration:
At this point, refreshing the WebUI should show a green Healthy status.
4 Creating a Bucket and Configuring Permissions
To use it as an image hosting service, 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 a Public Domain (Alias) & Enable Website Mode
This is the key to making images directly accessible via https://img.example.com/xxx.jpg.
You can also set the Alias and enable Website directly in the WebUI.

WebUI Interface
3. Create an API Key
- In the WebUI, click Keys -> Create Key (name it something like
blog-key). - Immediately save the Access Key ID and Secret Access Key that appear.
- Click Permissions on the right side of the Key, and check the Read/Write permissions 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 1Panel's "Websites" feature:
| Domain | Proxy Target | Purpose |
|---|---|---|
s3.example.com | http://127.0.0.1:3900 | S3 API (use this in PicList/Lsky) |
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 ports 80/443 for Nginx in 1Panel's firewall or your cloud provider's security group. Do not directly expose Garage's ports 3900-3903 to the public. Force all requests to go through the Nginx reverse proxy for better security.
6 Solving CORS Cross-Origin Issues
If you reference images on a blog or other website, the browser will block cross-origin requests. We need to force CORS headers in the Nginx configuration for img.example.com.
In 1Panel's website settings -> Configuration File, find the location / block and add the following:
Save and reload Nginx.
7 Client Connection Configuration
The service is now fully ready. Enter the following configuration in your image hosting tool:
- S3 Endpoint:
https://s3.example.com - Bucket:
photo - Access Key ID: (the ID obtained in Step 4)
- Secret Access Key: (the Secret obtained in Step 4)
- Region:
garage(default) - Force Path Style: Enabled (True)
- Custom Domain:
https://img.example.com