Deployment guide

Host Garage on a VPS: your own S3-compatible storage bucket

Deploy on a VPS Cloud →

Tutorial

Host Garage on a VPS: your own S3-compatible storage bucket

Self-hosting8 min read5 steps

You want an S3 storage bucket of your own — for restic backups, application files, or the origin of a static site — without signing up with a cloud provider. Garage, developed by the Deuxfleurs collective, does exactly that: a single Rust binary exposing an S3 API, lean enough to share a server with your other services. This guide takes you from zero to your first uploaded file, then covers common use cases and real pitfalls to avoid.

Contents· What Garage is, and what it is not1/10
  1. 01What Garage is, and what it is not
  2. 02What Garage does well on a VPS
  3. 03Prerequisites: sizing your VPS for a storage bucket
  4. 04Minimal storage bucket configuration
  5. 05Deploy Garage and create your first S3 bucket
  6. 06Common use cases for a self-hosted S3 storage bucket
  7. 07What people deploy behind Garage
  8. 08Garage vs self-hosted S3 alternatives
  9. 09Buckets, access keys and permissions
  10. 10Things to keep in mind

What Garage is, and what it is not

Garage is an S3-compatible object store designed for small self-hosted deployments, optionally distributed across multiple sites. It runs in a container, needs no adjacent database server, and on a single-node deployment it uses a few megabytes of memory at rest.

What it is not, let us be upfront: Garage has no web console. Administration goes through the garage command-line tool and an HTTP admin API. This is not a missing feature — it is a deliberate choice to keep the service small and its attack surface narrow. Community projects do provide a browser interface on top of the admin API if you need one.

If you are here because MinIO removed its console from the community edition in 2025 — and then stopped publishing community Docker images — the honest answer is that Garage will not give you that console back. It does give you a project whose AGPLv3 licence reserves nothing for a paid tier.

What Garage does well on a VPS

  • Standard S3 API: compatible with aws CLI, rclone, mc, restic, Python/Go/Rust SDKs and virtually all S3 clients on the market.
  • Minimal memory footprint: a few megabytes at rest — it coexists without friction with a web server, database or reverse proxy on the same VPS.
  • No external dependency: no PostgreSQL, no Redis, no etcd. The metadata engine is embedded (LMDB or SQLite).
  • Optional replication: in single-node mode a single disk is enough; in multi-node mode Garage replicates objects between machines, even across different sites.
  • Unencumbered AGPLv3 licence: no feature gated behind a paid edition, no volume or bucket count limits.
  • Virtual subdomains: Garage routes <bucket>.s3.your-domain.com, which some S3 clients and SDKs require.

Prerequisites: sizing your VPS for a storage bucket

Garage itself is frugal — it is the data volume you store that determines disk size, not the service. An entry-level VPS is enough to run the daemon. For typical workloads (application backups, site assets):

- CPU: 1 vCPU is sufficient; Garage is light on compute.
- RAM: 512 MB available is comfortable; the LMDB engine keeps its index in memory-mapped files.
- Disk: NVMe SSD sized for your data, plus 20 % headroom for metadata. A single node stores one copy of your objects — plan accordingly.
- OS: Debian 12 or Ubuntu 22.04 LTS, Docker and the Compose plugin installed.

To install Docker in one command:

curl -fsSL https://get.docker.com | sh

Minimal storage bucket configuration

Garage reads a garage.toml file. For a single node, the essentials fit in a few lines — note replication_factor = 1, which explicitly states there is no replication:

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"
rpc_secret = "<64 hexadecimal characters>"

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.your-domain.com"

[admin]
api_bind_addr = "[::]:3903"
admin_token = "<admin token>"

The rpc_secret must be 32 bytes, i.e. 64 hexadecimal characters: openssl rand -hex 32. The root_domain sets the subdomain routing scheme for bucket-based access — replace it with your actual domain.

Deploy Garage and create your first S3 bucket

  1. Create the directory structure

    Create a working directory and configuration file:

    mkdir -p /opt/garage/config
    openssl rand -hex 32   # → paste as rpc_secret
    openssl rand -base64 32 | tr -d '=' | head -c 40  # → admin_token

    Write garage.toml into /opt/garage/config/, replacing the two generated tokens.

  2. Write the Compose file and start

    Since version 2.3, Garage can bootstrap a single-node cluster and create its first bucket automatically from environment variables. This avoids the manual layout assign / layout apply / bucket create / key create sequence:

    services:
      garage:
        image: dxflrs/garage:v2.3.0
        restart: unless-stopped
        command: ["/garage", "-c", "/etc/garage/garage.toml", "server", "--single-node", "--default-bucket"]
        environment:
          GARAGE_DEFAULT_BUCKET: my-bucket
          GARAGE_DEFAULT_ACCESS_KEY: GK<30 hex>
          GARAGE_DEFAULT_SECRET_KEY: <64 hex>
        ports:
          - "127.0.0.1:3900:3900"
          - "127.0.0.1:3903:3903"
        volumes:
          - ./config:/etc/garage
          - garage-meta:/var/lib/garage/meta
          - garage-data:/var/lib/garage/data
    
    volumes:
      garage-meta:
      garage-data:

    The image is distroless: no shell, empty entrypoint. The command must name the binary by absolute path (/garage) and pass the config file (-c). Without both, the container loops on No such file or directory.

    The S3 access key must be GK followed by exactly 30 hexadecimal characters.

    docker compose up -d
  3. Verify the API is responding

    An anonymous call to the S3 API should return 403 — the correct sign that the service is alive and rejecting unsigned requests:

    curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3900/
    # 403 → API is up

    The admin API returns 200 with the token:

    curl -H "Authorization: Bearer $ADMIN_TOKEN" http://127.0.0.1:3903/v2/ListBuckets

    If you get connection refused, the container has not started — check docker compose logs garage.

  4. Expose the API behind nginx and TLS

    Never publish port 3900 directly on the public interface. Bind it to localhost and let nginx handle the proxy and TLS certificate. A minimal block for s3.your-domain.com:

    server {
        listen 443 ssl;
        server_name s3.your-domain.com *.s3.your-domain.com;
        ssl_certificate /etc/letsencrypt/live/s3.your-domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/s3.your-domain.com/privkey.pem;
        location / {
            proxy_pass http://127.0.0.1:3900;
            proxy_set_header Host $host;
        }
    }

    The *.s3.your-domain.com wildcard certificate covers per-bucket subdomains. Use certbot in DNS-01 mode to obtain it.

  5. Test with an S3 client

    With the environment variables set, the aws CLI works without modification:

    export AWS_ACCESS_KEY_ID=GK...
    export AWS_SECRET_ACCESS_KEY=...
    export AWS_DEFAULT_REGION=garage
    
    aws --endpoint-url https://s3.your-domain.com s3 ls
    aws --endpoint-url https://s3.your-domain.com s3 cp myfile.txt s3://my-bucket/

    For restic, set RESTIC_REPOSITORY=s3:https://s3.your-domain.com/my-bucket and run restic init.

Common use cases for a self-hosted S3 storage bucket

A Garage storage bucket on a VPS covers several common needs without depending on a third-party cloud.

What people deploy behind Garage

  • restic or Borg backups: set RESTIC_REPOSITORY=s3:https://s3.your-domain.com/backups. S3 compatibility in restic is native and requires no plugin.
  • Static site origin: store compiled frontend assets and serve them through nginx or Cloudflare in proxy mode. No signed S3 requests reach the end user.
  • Application file uploads: Laravel, Django or Rails apps write to Garage through their usual S3 SDK — only the endpoint changes.
  • CI/CD artefacts: GitHub Actions or GitLab CI push build artefacts to Garage with aws s3 cp, exactly as with AWS S3.
  • Log and metric archiving: Loki, Vector or Fluentd can write to an S3-compatible store for long-term archiving.
  • File sharing between services: two containers on the same or different VPS nodes access the same bucket via the S3 API, with no NFS mount.

Garage vs self-hosted S3 alternatives

Scroll the table

CriterionGarageMinIO (community)
LicenceAGPLv3 — no usage restrictionAGPLv3, but console removed from free edition in 2025
Docker imagesActively published on Docker Hub (`dxflrs/garage`)Community images discontinued on Docker Hub in 2025
Web consoleNone (CLI + admin API)Console removed from community edition
Multi-siteNatively designed for geographic zonesPossible but requires a dedicated cluster
Memory footprintA few MB at restSeveral dozen MB at minimum
S3 compatibilityGood on the common surfaceVery broad, including AWS-specific extensions

Buckets, access keys and permissions

Garage manages access at the key level: each S3 key can be authorised on one or more buckets, read-only or read-write. For automated backups, create a dedicated key with access to the backup bucket only — not the entire server.

The command garage key create <name> generates a key, and garage bucket allow --key <id> --read --write <bucket> grants it rights. You can also use the HTTP admin API to automate this in a script.

Things to keep in mind

A single node is not redundancy. Garage can replicate across multiple machines, even across different sites — that is its design goal. But as long as you have one node, your objects exist in a single copy on a single disk. Maintain the backup discipline you would apply regardless — backing up your backup bucket to a second location is not optional.

Expose the API behind a domain and TLS, not on a bare IP. It is both a security requirement and a practical necessity: several S3 clients refuse to operate without TLS, and per-bucket subdomain routing requires a wildcard certificate.

S3 compatibility covers the common surface, not every AWS-specific extension. Standard clients, libraries and backup tools work without modification; if you depend on an unusual call (Object Lambda, S3 Select, cross-account Replication…), check the project documentation before migrating.

Watch your disk space. A backup bucket that grows without a retention policy fills the disk silently. Set a lifecycle rule or a cron job to purge old snapshots via garage bucket lifecycle.

Garage in one click on your VPS

The ServOrbit Marketplace installs Garage, creates its bucket and your S3 credentials on first start. All you need to do is connect your client.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab