How to Run RustFS Object Storage in Docker and Compose

Run RustFS, the Apache-2.0 S3-compatible MinIO alternative, in Docker and Docker Compose with a healthcheck, the web console, aws-cli, boto3 and rclone backups.

RustFS is an S3-compatible object storage server written in Rust and released under the Apache-2.0 license. In this guide you will run RustFS in Docker and Docker Compose, persist data in a named volume, add a healthcheck, open the web console, talk to it with aws-cli, connect a Python app with boto3, and back the buckets up with rclone.

If you used MinIO for this before: the MinIO community edition is no longer maintained. Its GitHub repositories are archived, no community images or binaries have been published since October 2025, and dl.min.io now returns 410 Gone. How to Run MinIO Object Storage in Docker and Docker Compose has the details, and a maintained fork for existing MinIO deployments. For a new setup, RustFS is the simpler choice: one container, a permissive license, and the same S3 API your code already speaks.

Project status (as of November 2026): RustFS reached 1.0.0 on 2026-09-16 after a long beta and release-candidate series. This guide uses 1.0.4, the latest stable release on GitHub and Docker Hub. It is still a young project, so read the release notes before you bump the tag.

Prerequisites

You need Docker with the Compose plugin. See How to Install and Use Docker Engine on Ubuntu 26.04 LTS if you need to install it.

docker compose version

Pin the image tag

Pull a specific release rather than latest:

docker pull rustfs/rustfs:1.0.4

The rustfs/rustfs repository publishes latest, release tags such as 1.0.4, -glibc variants, and -preview.N pre-release builds. latest moves every time a release is cut, so a docker compose pull on a quiet Sunday can upgrade your storage server without you deciding to. Pin the tag and change it on purpose.

Run RustFS with Docker

Pick credentials first. These are placeholders, change both to long random values:

export RUSTFS_ACCESS_KEY="admin"
export RUSTFS_SECRET_KEY="CHANGE_ME_TO_A_LONG_RANDOM_SECRET"

Do not use rustfsadmin. It is the built-in default the server falls back to when no credentials are set, and the container logs a warning if you use it.

Start a single-node server:

docker run -d --restart unless-stopped \
  --name rustfs \
  -p 127.0.0.1:9000:9000 \
  -p 127.0.0.1:9001:9001 \
  -v rustfs-data:/data \
  -e "RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY}" \
  -e "RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY}" \
  rustfs/rustfs:1.0.4

What the options do:

  • -p 127.0.0.1:9000:9000 publishes the S3 API on localhost only, and 9001 is the web console. Drop the 127.0.0.1: prefix only when you mean to expose the server on your network.
  • -v rustfs-data:/data stores objects in a named Docker volume, so they survive container restarts and upgrades.
  • RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY are the root credentials for the S3 API and the console.

The image already sets RUSTFS_VOLUMES=/data, so no command or data path argument is needed.

Bind mounts and the container user

The container runs as a non-root user:

docker run --rm --entrypoint id rustfs/rustfs:1.0.4
uid=10001(rustfs) gid=10001(rustfs) groups=10001(rustfs)

A named volume handles ownership for you. If you bind-mount a host directory instead, the RustFS Docker docs say to make it owned by UID 10001, otherwise the server fails with permission denied errors:

sudo mkdir -p /srv/rustfs/data
sudo chown -R 10001:10001 /srv/rustfs/data

Then use -v /srv/rustfs/data:/data in place of the named volume.

Ports and endpoints

  • S3 API: http://127.0.0.1:9000
  • Web console: http://127.0.0.1:9001/rustfs/console/

Check that the server is up:

curl -fsS http://127.0.0.1:9000/health
{"service":"rustfs-endpoint","status":"ok","timestamp":"2026-11-03T12:13:51.76735791Z[Etc/Unknown]","version":"1.0.4"}

/health/live and /health/ready also return 200, and so do the MinIO-style /minio/health/live and /minio/health/ready paths, so existing load balancer checks keep working.

Where the logs go

The image sets RUSTFS_OBS_LOG_DIRECTORY=/logs, so docker logs rustfs only shows the entrypoint’s startup lines. The server log is a file inside the container:

docker exec rustfs tail /logs/rustfs.log

To send the logs to stdout, where docker logs and your log shipper can see them, set the variable to an empty value with -e RUSTFS_OBS_LOG_DIRECTORY=.

Run RustFS with Docker Compose

Stop the container from the previous section first, since it holds ports 9000 and 9001:

docker rm -f rustfs

Create a working directory:

mkdir -p ~/rustfs
cd ~/rustfs

Keep the credentials out of the Compose file. Create a .env file next to it, change the values, and do not commit it:

cat > .env <<'EOF'
RUSTFS_ACCESS_KEY=admin
RUSTFS_SECRET_KEY=CHANGE_ME_TO_A_LONG_RANDOM_SECRET
EOF
chmod 600 .env

Create compose.yaml:

services:
  rustfs:
    image: rustfs/rustfs:1.0.4
    restart: unless-stopped
    environment:
      RUSTFS_ACCESS_KEY: "${RUSTFS_ACCESS_KEY:?set RUSTFS_ACCESS_KEY in .env}"
      RUSTFS_SECRET_KEY: "${RUSTFS_SECRET_KEY:?set RUSTFS_SECRET_KEY in .env}"
      RUSTFS_ADDRESS: ":9000"
      RUSTFS_CONSOLE_ENABLE: "true"
      RUSTFS_CONSOLE_ADDRESS: ":9001"
      RUSTFS_VOLUMES: /data
      RUSTFS_OBS_LOG_DIRECTORY: ""
    ports:
      - "127.0.0.1:9000:9000"
      - "127.0.0.1:9001:9001"
    volumes:
      - rustfs-data:/data
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:9000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

volumes:
  rustfs-data:

A few notes on this file:

  • The :? syntax makes Compose refuse to start if a credential is missing, instead of silently falling back to the default.
  • RUSTFS_ADDRESS, RUSTFS_CONSOLE_ADDRESS and RUSTFS_VOLUMES repeat the defaults. They are spelled out so the file documents what the container listens on.
  • The healthcheck uses curl, which ships in the image. Other services can wait for storage with depends_on: { rustfs: { condition: service_healthy } }.

Start it and check the status:

docker compose up -d
docker compose ps
NAME              IMAGE                 COMMAND                  SERVICE   CREATED          STATUS                    PORTS
rustfs-rustfs-1   rustfs/rustfs:1.0.4   "/entrypoint.sh rust…"   rustfs    15 seconds ago   Up 15 seconds (healthy)   127.0.0.1:9000->9000/tcp, 127.0.0.1:9001->9001/tcp

Wait for (healthy) before you point clients at it.

docker compose down -v deletes the rustfs-data volume and every object in it. Use plain docker compose down to stop the stack.

Open the web console

Open http://127.0.0.1:9001/rustfs/console/ in your browser and log in with the access key and secret key from .env. The bare http://127.0.0.1:9001/ returns an AccessDenied XML error, so include the /rustfs/console/ path.

The console is on by default in 1.0.4. Port 9001 listens even without RUSTFS_CONSOLE_ENABLE. Set RUSTFS_CONSOLE_ENABLE=false to turn it off on a server that only needs the S3 API.

Use the AWS CLI with RustFS

RustFS speaks the S3 API, so you do not need a vendor-specific client. The official aws-cli image works without installing anything. Load the credentials from .env into your shell, then wrap the container in a shell function:

set -a; . ./.env; set +a

aws() {
  docker run --rm --network host \
    -v "$PWD:/work" -w /work \
    -e AWS_ACCESS_KEY_ID="$RUSTFS_ACCESS_KEY" \
    -e AWS_SECRET_ACCESS_KEY="$RUSTFS_SECRET_KEY" \
    -e AWS_DEFAULT_REGION=us-east-1 \
    amazon/aws-cli --endpoint-url http://127.0.0.1:9000 "$@"
}

--network host lets the CLI container reach the server on the host’s 127.0.0.1:9000. That works on Linux. On Docker Desktop (macOS or Windows), drop --network host and use --endpoint-url http://host.docker.internal:9000 instead. If you already have aws installed, skip the function and pass --endpoint-url http://127.0.0.1:9000 to it directly.

Create a bucket, upload a file and list it:

echo hello > hello.txt
aws s3 mb s3://my-bucket
aws s3 cp hello.txt s3://my-bucket/hello.txt
aws s3 ls s3://my-bucket/
make_bucket: my-bucket
upload: ./hello.txt to s3://my-bucket/hello.txt
2026-11-03 12:14:55          6 hello.txt

For more aws s3 commands, see Using AWS S3 from the terminal with awscli.

Connect an application

Any S3 SDK can talk to RustFS. Three settings matter, and they are the same in every language:

  1. Endpoint URL: point the client at http://127.0.0.1:9000 (or http://rustfs:9000 from another container on the same Compose network) instead of AWS.
  2. Path-style addressing: without extra configuration, RustFS serves path-style requests only (http://host:9000/bucket/key). The server’s own help text says so: virtual-hosted-style requests (bucket.host) need RUSTFS_SERVER_DOMAINS set and matching DNS. Force path style in the client.
  3. A region: RustFS has no regions and accepted every region name I tried, but SigV4 includes a region in every signature, and not every SDK or tool falls back to a default the way boto3 does. Set us-east-1 explicitly.

Keep these in environment variables so the same code runs against RustFS locally and real S3 in production:

S3_ENDPOINT_URL=http://127.0.0.1:9000
S3_BUCKET=app-uploads
S3_ACCESS_KEY_ID=admin
S3_SECRET_ACCESS_KEY=CHANGE_ME_TO_A_LONG_RANDOM_SECRET
S3_REGION=us-east-1

A Python example with boto3. It creates the bucket on first use, uploads an object and reads it back:

import boto3
from botocore.config import Config
from botocore.exceptions import ClientError

s3 = boto3.client(
    "s3",
    endpoint_url="http://127.0.0.1:9000",
    aws_access_key_id="admin",
    aws_secret_access_key="CHANGE_ME_TO_A_LONG_RANDOM_SECRET",
    region_name="us-east-1",
    config=Config(s3={"addressing_style": "path"}),
)

bucket = "app-uploads"
try:
    s3.head_bucket(Bucket=bucket)
except ClientError:
    s3.create_bucket(Bucket=bucket)

s3.put_object(Bucket=bucket, Key="hello.txt", Body=b"hello from boto3\n")
print(s3.get_object(Bucket=bucket, Key="hello.txt")["Body"].read().decode(), end="")
hello from boto3

In a real app, read those values from the environment instead of hard-coding them. Using AWS S3 Programatically with Python using Boto3 SDK covers the rest of the boto3 S3 API, and all of it works against RustFS.

Back up your buckets with rclone

The rustfs-data volume holds data that no database dump includes, so it needs its own backup. rclone can copy buckets to a local directory or another provider, and you can define the remote entirely in environment variables, with no rclone config and no config file on disk:

export RCLONE_CONFIG_RUSTFS_TYPE=s3
export RCLONE_CONFIG_RUSTFS_PROVIDER=Other
export RCLONE_CONFIG_RUSTFS_ENDPOINT=http://127.0.0.1:9000
export RCLONE_CONFIG_RUSTFS_ACCESS_KEY_ID="$RUSTFS_ACCESS_KEY"
export RCLONE_CONFIG_RUSTFS_SECRET_ACCESS_KEY="$RUSTFS_SECRET_KEY"
export RCLONE_CONFIG_RUSTFS_REGION=us-east-1
export RCLONE_CONFIG_RUSTFS_FORCE_PATH_STYLE=true

The RCLONE_CONFIG_RUSTFS_* prefix defines a remote named rustfs. PROVIDER=Other is rclone’s generic S3 setting, and the dummy region and forced path style are the same settings as in the app section.

List the buckets, then copy one to a local directory:

rclone lsd rustfs:
rclone copy rustfs:my-bucket ./backup/my-bucket
          -1 2026-11-03 15:15:12        -1 app-uploads
          -1 2026-11-03 15:14:54        -1 my-bucket

Use rclone copy, not rclone sync, for backups. sync makes the destination match the source, deletions included, so an object deleted by mistake in RustFS disappears from the backup on the next run. copy only adds and updates.

Conclusion

You now have RustFS running in Docker and Docker Compose on a pinned tag, with a named volume, a healthcheck and the web console. You also tested it with aws-cli, connected a boto3 app using path-style addressing and a placeholder region, and backed up a bucket with an environment-defined rclone remote. Before you rely on it, change the placeholder credentials, keep ports bound to 127.0.0.1 unless clients on other hosts need them, and schedule the rclone copy.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy