Garage
Overview
This guide covers the deployment and configuration of Garage on Ubuntu 24.04 on Azure using SecureAnyCloud Azure Marketplace images. Garage is a lightweight, self-hosted, S3-compatible distributed object storage server written in Rust by Deuxfleurs. It is designed to be simple to operate and efficient on modest hardware. It exposes the Amazon S3 wire protocol, so tools written for S3 (aws-cli, any aws-sdk, Restic, rclone, backup agents, static-site tooling, data pipelines) work against it unchanged.
The image ships Garage 2.3.0, the latest stable release, installed from the official static binary with its checksum pinned. A single garage process runs the S3 API, the admin API and the static web endpoint together with replication factor 1. This is a complete single-node object store on one virtual machine. Everything is bound to loopback, and an nginx TLS front publishes three endpoints:
S3 API on https://<vm-ip>:3900, authenticated with AWS SigV4 access and secret keys (region garage).
Web bucket endpoint on https://<vm-ip>:3902, for serving buckets configured for static website hosting.
Admin API on https://<vm-ip>:3903, protected by a Bearer token (GET /health is unauthenticated).
On every fresh customer virtual machine, garage-firstboot.service generates a per-VM RPC secret, admin token, metrics token, S3 access key and secret key, and a TLS certificate bound to the VM's IP. It then applies the single-node storage layout, creates a demo bucket with an owner key, and records everything to /root/garage-credentials.txt (mode 0600, root only). No two virtual machines ever share credentials, and there is no default login.
What you get:
- Garage 2.3.0 static binary at
/usr/local/bin/garage(S3 API + admin API + web endpoint in one server) garage.servicesystemd unit, gated on a first-boot marker so it never starts with a default secret, all listeners bound to 127.0.0.1- nginx TLS front:
:3900S3 API (SigV4),:3902web bucket serving,:3903admin API (Bearer token),:80plain-text info notice garage-firstboot.servicesystemd oneshot that generates per-VM secrets, applies the storage layout, creates a demo bucket + key, and rotates the TLS cert- A dedicated 40 GiB data volume mounted at
/var/lib/garagefor object metadata and data - AWS CLI v2 preinstalled at
/usr/local/bin/awsfor command-line S3 operations - Ubuntu 24.04 LTS base with latest security patches applied at build time
- Azure Linux Agent for seamless cloud integration and SSH key injection
- 24/7 SecureAnyCloud support with guaranteed 24 hour response SLA
Prerequisites
You need:
- An active Azure subscription
- A subscription to the Garage on Ubuntu 24.04 listing on Azure Marketplace
- An SSH public key for VM authentication
- A virtual network and subnet in the target region
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development and small workloads. Production deployments should use Standard_D2s_v3 or larger and enlarge or add data disks mounted at /var/lib/garage for capacity and throughput.
Deploy from the Azure Portal
Open the Marketplace in the Azure Portal, search for Garage, select the SecureAnyCloud publisher entry, and select Create.
On the Networking tab, attach a network security group that allows inbound TCP 22 from your management IP range and TCP 3900 (the S3 API) from your application networks. Allow TCP 3902 (web serving) and TCP 3903 (admin API) from your admin networks only if you need them remotely. The internal Garage ports are bound to loopback and are never exposed directly.
Select Review + create, wait for validation, and then select Create. Deployment takes around two minutes.
Deploy from the Azure CLI
RG="garage-prod" LOCATION="eastus" VM_NAME="garage-01" ADMIN_USER="azureuser" GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-sac/providers/Microsoft.Compute/galleries/sacGallery/images/garage-ubuntu-24-04/versions/<version>" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create \
--resource-group "$RG" \
--name garage-vnet --address-prefix 10.96.0.0/16 \
--subnet-name garage-subnet --subnet-prefix 10.96.1.0/24 az network nsg create --resource-group "$RG" --name garage-nsg az network nsg rule create \
--resource-group "$RG" --nsg-name garage-nsg \
--name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" \
--destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create \
--resource-group "$RG" --nsg-name garage-nsg \
--name allow-s3-api --priority 110 \
--source-address-prefixes 10.96.0.0/16 \
--destination-port-ranges 3900 --access Allow --protocol Tcp az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username "$ADMIN_USER" --ssh-key-values "$SSH_KEY" \
--vnet-name garage-vnet --subnet garage-subnet --nsg garage-nsg \
--public-ip-sku StandardConnect via SSH
ssh azureuser@<vm-ip>garage.service and nginx.service will already be running, and garage-firstboot.service will already have generated the per-VM credentials, applied the storage layout, and created the demo bucket.
Verify the Services
sudo systemctl is-active garage.service nginx.serviceThe expected output is active twice. Confirm the first-boot sentinel:
sudo test -f /var/lib/sac/data/garage-firstboot.done && echo FIRSTBOOT_DONEConfirm the public TLS listeners and that the internal Garage ports are bound to loopback only:
sudo ss -tln | grep -E ':(3900|3901|3902|3903|3910|3912|3913) 'You will see 0.0.0.0:3900, 0.0.0.0:3902 and 0.0.0.0:3903 (public, TLS) while the S3 backend 3910, web 3912, admin 3913 and RPC 3901 are bound to 127.0.0.1.
Retrieve the Per-VM Credentials
sudo cat /root/garage-credentials.txtYou will see the per-VM S3 access key, S3 secret key, the admin and metrics tokens, and the endpoint URLs:
S3_ACCESS_KEY=<S3_ACCESS_KEY> S3_SECRET_KEY=<S3_SECRET_KEY> S3_REGION=garage S3_ENDPOINT_URL=https://<vm-ip>:3900 WEB_ENDPOINT_URL=https://<vm-ip>:3902 ADMIN_ENDPOINT_URL=https://<vm-ip>:3903 ADMIN_TOKEN=<ADMIN_TOKEN> METRICS_TOKEN=<METRICS_TOKEN> DEMO_BUCKET=sacStore these values in your secret store. The file is mode 0600 (root only).
sudo /usr/local/bin/garage --versionInspect the Cluster Status and Layout
The garage CLI reads /etc/garage.toml and talks to the local node over RPC. Confirm the single node is healthy and the storage layout is applied:
sudo garage -c /etc/garage.toml statussudo garage -c /etc/garage.toml layout showYou will see one healthy node in zone dc1 with its capacity, and a current cluster layout at version 1.
Round-trip Test from the CLI
The AWS CLI is preinstalled. The following command reads the per-VM S3 keys straight from the credentials file and performs a full create-bucket, put, get, verify, delete round-trip through the TLS S3 endpoint. Garage validates SigV4 with region garage, so the client must sign with that region:
export AWS_ACCESS_KEY_ID="$(sudo awk -F= '/^S3_ACCESS_KEY=/{print $2}' /root/garage-credentials.txt)"
export AWS_SECRET_ACCESS_KEY="$(sudo awk -F= '/^S3_SECRET_KEY=/{print $2}' /root/garage-credentials.txt)"
export AWS_DEFAULT_REGION="garage" EP="--no-verify-ssl --endpoint-url https://127.0.0.1:3900" aws $EP s3 mb s3://sac-demo echo "sac-roundtrip-ok" > /tmp/probe.txt aws $EP s3 cp /tmp/probe.txt s3://sac-demo/probe.txt aws $EP s3 cp s3://sac-demo/probe.txt /tmp/probe-back.txt cat /tmp/probe-back.txt aws $EP s3 rm s3://sac-demo/probe.txt aws $EP s3 rb s3://sac-demoExpected output includes make_bucket, the upload and download lines, sac-roundtrip-ok, then clean removals. The --no-verify-ssl flag is only needed for the self-signed certificate on a first local connection.
List Buckets and Objects
export AWS_ACCESS_KEY_ID="$(sudo awk -F= '/^S3_ACCESS_KEY=/{print $2}' /root/garage-credentials.txt)"
export AWS_SECRET_ACCESS_KEY="$(sudo awk -F= '/^S3_SECRET_KEY=/{print $2}' /root/garage-credentials.txt)"
export AWS_DEFAULT_REGION="garage" aws --no-verify-ssl --endpoint-url https://127.0.0.1:3900 s3 lsThe SecureAnyCloud demo bucket created at first boot is listed. To list the objects inside a bucket, add the bucket name:
export AWS_ACCESS_KEY_ID="$(sudo awk -F= '/^S3_ACCESS_KEY=/{print $2}' /root/garage-credentials.txt)"
export AWS_SECRET_ACCESS_KEY="$(sudo awk -F= '/^S3_SECRET_KEY=/{print $2}' /root/garage-credentials.txt)"
export AWS_DEFAULT_REGION="garage" aws --no-verify-ssl --endpoint-url https://127.0.0.1:3900 s3 ls s3://sac --recursive || trueQuery the Admin API
GET /health is unauthenticated and returns 200 when the node is ready. The richer endpoints require the per-VM admin token as an HTTP Bearer token:
curl -ks -o /dev/null -w 'health: HTTP %{http_code}\n' https://127.0.0.1:3903/healthADMIN_TOKEN="$(sudo awk -F= '/^ADMIN_TOKEN=/{print $2}' /root/garage-credentials.txt)" curl -ks -H "Authorization: Bearer ${ADMIN_TOKEN}" https://127.0.0.1:3903/v2/GetClusterHealthThe cluster health response reports "status":"healthy" with the partition counts for the single node.
Connect from Application Code
Any aws-sdk works. The following Python boto3 example points at the public S3 endpoint with the per-VM keys and region garage:
python3 -c " import boto3, urllib3 urllib3.disable_warnings() s3 = boto3.client( 's3', endpoint_url='https://<vm-ip>:3900', aws_access_key_id='<S3_ACCESS_KEY>', aws_secret_access_key='<S3_SECRET_KEY>', region_name='garage', verify=False, ) print(s3.list_buckets()) "For production, install the per-VM certificate (/etc/garage/tls/garage.crt) into your client's trust store instead of disabling verification, or front Garage with a certificate from a public CA.
Create Your Own Buckets and Keys
New buckets and access keys are managed with the garage CLI over SSH. Create a bucket, mint an application key, and grant it access:
sudo garage -c /etc/garage.toml bucket create my-app
# <your-token>
sudo garage -c /etc/garage.toml key create my-app-key
# <your-token>
sudo garage -c /etc/garage.toml bucket allow --read --write my-app --key my-app-key
# <your-token>The key create command prints the new access key ID and secret once. Store them in your secret store immediately.
Server Components
Components and paths:
garage binary /usr/local/bin/garage
AWS CLI v2 /usr/local/bin/aws
Configuration file /etc/garage.toml
Data volume (metadata + data) /var/lib/garage (meta/, data/)
TLS certificate + key /etc/garage/tls/garage.crt, garage.key
nginx site /etc/nginx/sites-available/garage
Systemd unit /etc/systemd/system/garage.service
Firstboot script /usr/local/sbin/garage-firstboot.sh
Firstboot service /etc/systemd/system/garage-firstboot.service
Credentials file /root/garage-credentials.txt
Firstboot sentinel /var/lib/sac/data/garage-firstboot.done
Managing the Service
Status:
sudo systemctl status garage.service --no-pager | head -n 8Restart (both the server and the TLS front):
sudo systemctl restart garage.service
sudo systemctl restart nginx.serviceView recent activity (systemd journal):
sudo journalctl -u garage.service --no-pager -n 30 | tail -8Production Hardening
The shipped image is single-node, which suits development, staging, and small to medium workloads. For production deployments:
- Grow the data volume. The object metadata and data live on the dedicated disk at
/var/lib/garage, so it scales independently of the OS disk. Expand it or attach a larger Premium SSD as capacity grows, and then update the layout capacity withgarage layout assign. - Restrict the admin and web ports. Expose TCP 3903 (admin) and 3902 (web) only to the networks that need them, or keep them internal and reach the admin API over an SSH tunnel.
- Use a CA-signed certificate. Replace the per-VM self-signed certificate at
/etc/garage/tls/with one from a public CA or your internal PKI, so clients do not need--no-verify-ssl. - Rotate credentials on a schedule. Mint fresh application keys with
garage key createand delete old ones withgarage key delete. Rotate the admin token by editing/etc/garage.tomland restarting the service.
Security Model
- No default login. The RPC secret, admin token, metrics token and S3 access/secret key are all generated uniquely on each virtual machine at first boot. Nothing is baked into the image.
- Gated startup.
garage.serviceonly starts after first boot has written the per-VM configuration and secrets, so the service can never come up with a build-time secret. - Loopback by default. Every Garage listener is bound to
127.0.0.1. Only the nginx TLS front is public, and only on the S3, web and admin ports. - Root-only credentials.
/root/garage-credentials.txtis mode 0600, readable only by root.
Support
SecureAnyCloud provides 24/7 support with a guaranteed 24 hour response SLA. For assistance with this image, contact sales@secureanycloud.com.
Deploy on Azure
Launch Garage on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com