Skip to content

Instantly share code, notes, and snippets.

@Azeirah
Last active February 20, 2026 14:06
Show Gist options
  • Select an option

  • Save Azeirah/54eb078d075b9905d49aab7dfbeb2977 to your computer and use it in GitHub Desktop.

Select an option

Save Azeirah/54eb078d075b9905d49aab7dfbeb2977 to your computer and use it in GitHub Desktop.
Garage s3 for local development simulation
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=garage
AWS_BUCKET=development
# could be more flexible if the port was put into its own environment variable
AWS_ENDPOINT=http://localhost:3900
AWS_USE_PATH_STYLE_ENDPOINT=true

Running a local S3 server

When working with photos or laravel-medialibrary in general, you need to initialize the garage_s3 docker container:

bash .docker/garage_s3/init_garage.sh

The command will show the generated ACCESS_KEY and SECRET_KEY, which you need to copy into your .env file:

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_USE_PATH_STYLE_ENDPOINT=true   # Garage buckets are accessed with path-style endpoints
AWS_BUCKET=development             # The bucket is called `development`, it's set in the init script
AWS_DEFAULT_REGION=garage          # Garage for local development doesn't have regions
AWS_ENDPOINT=http://localhost:3900 # Depends on your docker-compose setup and env vars

You can use awscli or other compatible tools to interact with the s3 bucket.

See the Garage documentation on how to interact with the s3 bucket.

Test whether your Laravel and Garage is set up correctly with Tinker. If everything is configured correctly, you should see:

$ sail art tinker
> Storage::disk('s3')->files()
= []
{
"CORSRules": [
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
}
garage_s3:
image: 'dxflrs/garage:v2.2.0'
ports:
- '${GARAGE_S3_API_PORT:-3900}:3900'
- '${GARAGE_S3_RPC_PORT:-3901}:3901'
- '${GARAGE_S3_WEB_PORT:-3902}:3902'
networks:
- sail
volumes:
- '.docker/garage_s3/garage.toml:/etc/garage.toml'
- 'garage-meta:/var/lib/garage/meta'
- 'garage-data:/var/lib/garage/data'
- /
- docker-compose.yml
- .env
- README.md
- .docker/garage_s3/
- cors.json
- garage.toml
- garage_init.sh
metadata_dir = "/tmp/meta"
data_dir = "/tmp/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
# _must_ be 32 bytes of random hex. This is obviously not a secure secret.
# It is meant for development only.
rpc_secret = "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.garage.localhost"
index = "index.html"
#!/usr/bin/env bash
# This is just Laravel sail (docker) specific, don't worry about it
export WWWGROUP="${WWWGROUP:-$(id -g)}"
export WWWUSER="${WWWUSER:-$(id -u)}"
# These variables should probably be configurable via the .env file, but they work for our set-up
SERVICE="garage_s3"
BUCKET="development"
KEY_NAME="my-key"
ZONE="dev"
CAPACITY="4G"
ENDPOINT="http://localhost:3900"
CORS_CONFIG="$(dirname "$0")/cors.json"
garage() {
docker compose exec -e RUST_LOG=error "$SERVICE" /garage "$@"
}
if ! command -v aws &>/dev/null; then
echo "Error: AWS CLI not found in PATH."
echo "Install it via your package manager or https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
echo "Then make sure it is available in your PATH and rerun this script."
exit 1
fi
# This feels slightly suboptimal, probably better to just exit and let the user try again rather than sleep.
# But it works fine.
echo "Waiting for Garage to start..."
until garage status &>/dev/null; do
sleep 1
done
if garage bucket list 2>/dev/null | grep -q "$BUCKET"; then
echo "Garage already initialized, skipping."
echo "If you want to reconfigure garage: run, start the garage_s3 container and rerun the init script"
echo " docker compose down -v garage_s3"
echo " docker compose up -d garage_s3"
echo " bash .docker/garage_s3/garage_init.sh"
exit 0
fi
echo "Initializing Garage..."
# 1. Create layout
echo "1. Creating a layout"
NODE_ID=$(garage status 2>/dev/null | awk '/^[a-f0-9]/{print $1; exit}')
echo "Node ID: $NODE_ID"
garage layout assign -z "$ZONE" -c "$CAPACITY" "$NODE_ID"
# 2. Apply the layout (first version is always 1)
echo "2. Applying the layout"
garage layout apply --version 1
# 3. Create bucket
echo "3. Creating the 'development' bucket"
garage bucket create "$BUCKET"
# 4. Create API key and capture credentials
echo "4. Creating an API key"
KEY_OUTPUT=$(garage key create "$KEY_NAME")
ACCESS_KEY=$(echo "$KEY_OUTPUT" | awk '/Key ID/{print $NF}')
SECRET_KEY=$(echo "$KEY_OUTPUT" | awk '/Secret key/{print $NF}')
# 5. Grant access
echo "5. Granting the created API key access to the development bucket"
garage bucket allow --read --write --owner "$BUCKET" --key "$KEY_NAME"
echo "6. Configuring CORS on the '$BUCKET' bucket"
AWS_ACCESS_KEY_ID="$ACCESS_KEY" \
AWS_SECRET_ACCESS_KEY="$SECRET_KEY" \
aws s3api put-bucket-cors \
--bucket "$BUCKET" \
--cors-configuration "file://$CORS_CONFIG" \
--endpoint-url "$ENDPOINT" \
--region garage
echo "Garage initialized successfully!"
echo ""
echo " Bucket: $BUCKET"
echo " Access Key: $ACCESS_KEY"
echo " Secret Key: $SECRET_KEY"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment