Deployment guide

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.service systemd 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: :3900 S3 API (SigV4), :3902 web bucket serving, :3903 admin API (Bearer token), :80 plain-text info notice
  • garage-firstboot.service systemd 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/garage for object metadata and data
  • AWS CLI v2 preinstalled at /usr/local/bin/aws for 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.

1

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.

2

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 Standard
3

Connect 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.

4

Verify the Services

sudo systemctl is-active garage.service nginx.service

The expected output is active twice. Confirm the first-boot sentinel:

sudo test -f /var/lib/sac/data/garage-firstboot.done && echo FIRSTBOOT_DONE

Confirm 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.

5

Retrieve the Per-VM Credentials

sudo cat /root/garage-credentials.txt

You 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=sac

Store these values in your secret store. The file is mode 0600 (root only).

sudo /usr/local/bin/garage --version
6

Inspect 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 status
sudo garage -c /etc/garage.toml layout show

You will see one healthy node in zone dc1 with its capacity, and a current cluster layout at version 1.

7

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-demo

Expected 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.

8

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 ls

The 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 || true
9

Query 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/health
ADMIN_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/GetClusterHealth

The cluster health response reports "status":"healthy" with the partition counts for the single node.

10

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.

11

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.

12

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

13

Managing the Service

Status:

sudo systemctl status garage.service --no-pager | head -n 8

Restart (both the server and the TLS front):

sudo systemctl restart garage.service
sudo systemctl restart nginx.service

View recent activity (systemd journal):

sudo journalctl -u garage.service --no-pager -n 30 | tail -8
14

Production 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 with garage 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 create and delete old ones with garage key delete. Rotate the admin token by editing /etc/garage.toml and 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.service only 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.txt is 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

Deploy on Azure