Introduction
If you've ever set up Nextcloud, Immich, or a backup job with restic, you've probably run into a setting that asks for an "S3 endpoint." Most of the time that means paying Amazon, Backblaze, or Wasabi for storage you could just as easily keep on your own hardware. Proxmox VE already gives you local disks, ZFS, and NFS — but none of those speak the S3 protocol that a growing number of self-hosted apps now expect out of the box.
That's where an S3-compatible object store comes in, and specifically, where Garage comes in. Garage is a small, open-source storage server built by a French research collective called Deuxfleurs. It's not trying to be a full cloud platform — it does one job, S3-compatible object storage, and it does it on hardware as modest as a Raspberry Pi. That makes it a genuinely good fit for a homelab LXC container.
You might have heard of MinIO for this same job. Worth knowing before you go looking for a MinIO tutorial: MinIO ended active development of its free Community Edition in early 2026 and archived the GitHub repository that April, pushing new users toward its paid AIStor product instead. Garage never had that problem — it's AGPLv3, actively maintained, and was built for exactly this kind of small, single-node deployment from day one. This guide walks through installing it in its own LXC container on Proxmox VE, from an empty container to a working bucket you can actually upload a file to.
What You Will Learn
- What an S3-compatible object store actually does and why a homelab would want one
- How to create a Debian 13 LXC container on Proxmox VE for Garage
- How to download, configure, and run Garage as a systemd service
- How to create your first bucket and access key
- How to test the setup with a real file upload
- The errors you're most likely to hit and how to fix them
What Is This Feature?
Before the commands, a few terms are worth pinning down, because the rest of the guide leans on them.
Object storage is a way of storing files (called "objects") in flat buckets instead of a traditional folder tree. There's no real concept of directories — just a bucket name and a key, which is usually a path-like string. It scales differently than a filesystem and it's what most cloud backup tools and photo apps expect when they ask for "S3 storage."
S3 refers to Amazon's Simple Storage Service, and more specifically to the HTTP API it popularized. That API became a de facto standard — dozens of tools now talk "S3" without ever touching Amazon, as long as something on the other end understands the same requests. Garage is one of those somethings.
LXC stands for Linux Containers. Unlike a full virtual machine, an LXC container shares the host's kernel and only virtualizes user space, so it starts in a couple of seconds and uses a fraction of the RAM a VM would need for the same job. Proxmox VE uses LXC for exactly this kind of lightweight, single-purpose service.
systemd is the service manager built into Debian and most modern Linux distributions. It's what starts Garage when the container boots, restarts it if it crashes, and gives you commands like systemctl status to check on it.
Garage itself is a single static binary. No database server, no external dependencies — you download one file, write a small config, and point systemd at it.
Why Would You Use It?
The honest answer: because something you're already running wants an S3 endpoint and you'd rather not send that data to a cloud provider. A few concrete examples come up constantly in homelab setups:
- Proxmox Backup Server can use an S3-compatible bucket as a datastore backend. Garage running on cheap local disks is a lot less scary to point that at than trying it live on a public cloud bill for the first time.
- Immich, the self-hosted photo app, supports external S3 storage for your photo library instead of keeping everything on the container's own disk.
- restic and rclone both back up to S3 targets natively, which means Garage can become the destination for backups from other machines on your network.
- Static websites — Garage can serve a bucket's contents directly over HTTP, which is handy for a Hugo or Jekyll site you don't want to pay S3 or CloudFront for.
None of this requires a cluster. A single Garage node with replication_factor = 1 is genuinely fine for testing, for a homelab where you already back up the underlying disk, or for data you can afford to lose. Just don't mistake it for redundant storage — more on that in Best Practices.
Prerequisites
- A working Proxmox VE 8.x or 9.x host with a Debian 13 (Trixie) LXC template downloaded, or the ability to download one
- At least 2 GB of free RAM and 8 GB of free disk space on the storage you'll use for the container
- Basic comfort with the Linux command line — this guide runs entirely from the container's shell, no GUI steps involved
- A rough idea of how much data you'll actually store, since that decides how big to make the container's disk
Step-by-Step Tutorial
Step 1: Create the LXC Container
From the Proxmox web interface, click Create CT, or do it from the shell. Either way, you'll need the Debian 13 template first. Check what's actually available before you guess a filename, since the exact build number changes as new templates ship:
pveam update
pveam available --section system | grep debian-13
pveam download local debian-13-standard_13.5-1_amd64.tar.zst
Then create the container. Adjust the VMID, storage names, and bridge to match your own setup:
pct create 200 local:vztmpl/debian-13-standard_13.5-1_amd64.tar.zst \
--hostname garage \
--cores 2 \
--memory 2048 \
--swap 512 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--features nesting=0 \
--start 1
Two vCPUs and 2 GB of RAM is comfortable headroom for a single-node Garage instance handling homelab-scale traffic — it'll idle at a fraction of that. The 8 GB root disk is for the OS and Garage's metadata, not your actual object data; if you're planning to store a lot, look at the mount-point tip in Best Practices before you fill this disk up.
Step 2: Update the Container and Install Basic Tools
Log into the container's console from the Proxmox GUI, or SSH in once it has an IP address. Then bring it up to date and install the couple of tools Garage's setup relies on:
apt update && apt full-upgrade -y
apt install -y curl openssl
curl downloads the Garage binary in the next step, and openssl generates the random secrets Garage needs for its config file.
Step 3: Download the Garage Binary
Garage ships as a single static binary with no package for Debian, so you download it directly from the project's release server. As of this writing the current stable release is v2.3.0:
curl -o /usr/local/bin/garage \
https://garagehq.deuxfleurs.fr/_releases/v2.3.0/x86_64-unknown-linux-musl/garage
chmod +x /usr/local/bin/garage
Confirm it runs before moving on:
garage --version
If that prints a version string instead of a "command not found" error, you're good to continue. Check the Garage download page occasionally for newer releases — the URL pattern stays the same, just the version number changes.
Step 4: Create the Storage Directories
Garage keeps two things separate: metadata (bucket and key information) and the actual object data. Give each its own directory on persistent storage, not /tmp:
mkdir -p /var/lib/garage/meta /var/lib/garage/data
Step 5: Write the Configuration File
Generate two secrets first — one for inter-node communication, one for the admin API — and write them straight into the config:
RPC_SECRET=$(openssl rand -hex 32)
ADMIN_TOKEN=$(openssl rand -base64 32)
cat > /etc/garage.toml /etc/systemd/system/garage.service test.txt
aws --profile garage --endpoint-url http://127.0.0.1:3900 \
s3 cp test.txt s3://my-first-bucket/
List the bucket to confirm it landed:
aws --profile garage --endpoint-url http://127.0.0.1:3900 \
s3 ls s3://my-first-bucket/
If test.txt shows up, your Garage node is working end to end. From here, any app that accepts an S3 endpoint, access key, and secret key can point at http://<container-ip>:3900.
Commands Explained
| Command | What It Does |
|---|---|
garage status | Shows connected nodes, their health, and the current layout version |
garage layout assign | Adds a node to the cluster layout with a zone and storage capacity |
garage layout apply | Commits a pending layout change so the cluster starts using it |
garage bucket create | Creates a new, empty bucket |
garage key create | Generates a new S3 access key and secret key pair |
garage bucket allow | Grants a key read, write, or owner permissions on a bucket |
systemctl status garage | Shows whether the service is running and its recent log lines |
journalctl -u garage -f | Streams Garage's logs in real time, useful while debugging |
Common Errors
"Cannot open config file /etc/garage.toml" — Garage looks for its config at that exact path by default. If you wrote the file somewhere else, either move it there or set GARAGE_CONFIG_FILE in the systemd unit's Environment= line.
"Error: no nodes with role assigned in layout" — this means Step 7 was skipped or the layout was never applied. Run garage status to check, then repeat the layout assign and layout apply commands.
Connection refused on port 3900 — usually means the service isn't running at all. Check systemctl status garage first; if it's active, double-check you're hitting the container's IP and not the Proxmox host's IP by mistake.
"SignatureDoesNotMatch" from the AWS CLI — almost always a mismatched region. The --region value in your AWS CLI profile has to match s3_region in garage.toml exactly, including capitalization.
"AccessDenied" when uploading — the key exists but hasn't been granted permission on that specific bucket. Re-run garage bucket allow with the correct key and bucket names; permissions in Garage are per-bucket, not global.
Troubleshooting
Start with the logs. journalctl -u garage -n 50 --no-pager shows the last 50 lines, which almost always names the exact problem — a bad TOML value, a permissions issue on the data directory, or a port already in use.
If the service won't even start, verify the config file is valid TOML by eye — a missing quote or bracket is the most common cause, and Garage's error messages usually point at the line number.
If garage status hangs instead of returning quickly, the admin API probably isn't reachable. Confirm the service is actually running, and that nothing else in the container is already bound to port 3901 or 3900.
Firewall rules can also bite you here. If you're testing from another machine on your network rather than from inside the container itself, make sure the Proxmox VE firewall (if enabled on that container or bridge) isn't silently dropping traffic to ports 3900 and 3903.
Best Practices
- Don't treat single-node Garage as backed up. With
replication_factor = 1, a disk failure takes your data with it. If it matters, back up/var/lib/garagethe same way you'd back up anything else on that container. - Give it a real disk for data, not the root filesystem. If you're storing more than a few gigabytes, attach a second disk with
pct set 200 -mp0 /path/on/host,mp=/data/garagefrom the Proxmox host, and pointdata_dirat the mounted path instead. - Keep the admin token private. It grants full control over the cluster's layout and keys — treat it like a root password, not a config detail.
- Don't expose port 3900 directly to the internet. If you need remote access, put a reverse proxy with TLS in front of it, the same way you would for any other self-hosted service.
- Snapshot the container before major changes. Garage upgrades are generally smooth, but a Proxmox snapshot before bumping versions costs you nothing and saves a headache if something goes sideways.
Frequently Asked Questions
Is Garage a drop-in replacement for MinIO?
For basic S3 operations like buckets, objects, and access keys, yes. Garage doesn't implement every API MinIO once did, so check the S3 compatibility page in Garage's docs if your specific app needs something unusual.
Can I run Garage on the same LXC container as another app?
You can, but it's not recommended. Object storage benefits from a stable, predictable disk, and mixing it with an app that does heavy unrelated I/O just makes both harder to troubleshoot.
Does Garage need a GPU or special hardware?
No. It's written in Rust and runs comfortably on modest CPUs, including ARM boards like a Raspberry Pi.
What happens if I lose the admin token?
You lose remote administrative access through the admin API, but the running service and existing keys keep working. You can generate a new token and update the config, then restart the service.
Can I add more nodes later for redundancy?
Yes — that's a normal Garage deployment pattern. You'd set replication_factor to 2 or 3 and add nodes to the layout with garage layout assign, though that's beyond what a single homelab container needs to start.
Is the AWS CLI required, or can I use something else?
The AWS CLI is just what this guide uses to test uploads. Any S3-compatible client works, including rclone, s3cmd, or the S3 settings built into apps like Immich and restic.
Conclusion
You now have a working, self-hosted S3 endpoint running in a couple hundred megabytes of RAM inside a Proxmox VE container. It won't win benchmarks against a distributed cloud object store, and with a single node it isn't redundant — but for pointing Immich, restic, or a Proxmox Backup Server datastore at something you control, it does the job without a monthly bill or a licensing page to read. If you outgrow it, Garage's own multi-node cluster guide is the next stop, and none of the bucket or key setup you did here needs to change.