Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Build and push image

# Builds insectai/minio for linux/amd64 and linux/arm64 from the sources pinned in versions.env.
# Every run first builds linux/amd64 into the runner's Docker daemon and runs test/smoke.sh against it.
# Pull requests only build (no push). Pushes to main and manual runs build and push.

on:
Expand All @@ -9,11 +10,17 @@ on:
paths:
- Dockerfile
- versions.env
- docker-entrypoint.sh
- minio-healthcheck
- test/**
- .github/workflows/build.yml
pull_request:
paths:
- Dockerfile
- versions.env
- docker-entrypoint.sh
- minio-healthcheck
- test/**
- .github/workflows/build.yml
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -56,6 +63,31 @@ jobs:

- uses: docker/setup-buildx-action@v3

- name: Build for the smoke test (linux/amd64, loaded locally)
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
load: true
push: false
tags: insectai/minio:smoke
build-args: |
GO_IMAGE=${{ env.GO_IMAGE }}
RUNTIME_IMAGE=${{ env.RUNTIME_IMAGE }}
MINIO_REPO=${{ env.MINIO_REPO }}
MINIO_TAG=${{ env.MINIO_TAG }}
MINIO_COMMIT=${{ env.MINIO_COMMIT }}
MC_REPO=${{ env.MC_REPO }}
MC_TAG=${{ env.MC_TAG }}
MC_COMMIT=${{ env.MC_COMMIT }}
SOURCE_REVISION=${{ github.sha }}
provenance: false
sbom: false
cache-from: type=gha

- name: Smoke test
run: test/smoke.sh insectai/minio:smoke

- name: Log in to Docker Hub
if: steps.mode.outputs.push == 'true'
uses: docker/login-action@v3
Expand Down
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ LABEL org.opencontainers.image.title="MinIO server and mc client (community buil
org.insectai.mc.commit="${MC_COMMIT}"
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /out/ /
# The entrypoint creates MINIO_DEFAULT_BUCKETS at startup; the health check reports healthy only once the
# server is ready and those buckets exist. See README "Creating buckets at startup".
COPY --chmod=0755 docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
COPY --chmod=0755 minio-healthcheck /usr/bin/minio-healthcheck
EXPOSE 9000 9001
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/minio"]
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=12 CMD ["/usr/bin/minio-healthcheck"]
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["server", "/data", "--console-address", ":9001"]
52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ The `insectai` organisation is a Docker-Sponsored Open Source namespace, so anon
|---|---|---|
| `/usr/bin/minio` | [pgsty/silo](https://github.com/pgsty/silo), the community-maintained fork of `minio/minio` | Built with the fork's own `gen-ldflags.go`, so `minio --version` reports the pinned release. |
| `/usr/bin/mc` | [pgsty/mc](https://github.com/pgsty/mc), the matching fork of `minio/mc` | Same command set as upstream `mc` (`alias set`, `mb`, `anonymous set`, `ready`). |
| `/bin/sh` and BusyBox | Alpine base | Lets an init container run a shell script with `mc`. |
| `/usr/bin/docker-entrypoint.sh` | This repository | Starts the server and creates the buckets listed in `MINIO_DEFAULT_BUCKETS`. |
| `/usr/bin/minio-healthcheck` | This repository | The image's health check: healthy once the server is ready and those buckets exist. |
| `/bin/sh` and BusyBox | Alpine base | Runs the two scripts above, and lets you run your own shell scripts with `mc`. |
| `/licenses/` | Copied from the source checkouts | AGPL-3.0 licence, NOTICE and CREDITS files. |

The binaries keep their original names (`minio`, `mc`) so existing compose files and scripts work unchanged. The image runs as root by default, like the historical official image, and `/data` is world-writable so a non-root `user:` also works on a fresh volume.
Expand All @@ -35,16 +37,49 @@ docker buildx imagetools inspect insectai/minio:RELEASE.2026-09-16T00-00-00Z
services:
minio:
image: insectai/minio:RELEASE.2026-09-16T00-00-00Z@sha256:<digest>
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: change-me-please
MINIO_DEFAULT_BUCKETS: media:public,backups
healthcheck:
test: ["CMD", "mc", "ready", "local"]
minio-init:
image: insectai/minio:RELEASE.2026-09-16T00-00-00Z@sha256:<digest>
entrypoint: ["/bin/sh", "/etc/minio/init.sh"]
test: ["CMD", "minio-healthcheck"]
interval: 5s
retries: 12

app:
depends_on:
minio:
condition: service_healthy
```

The default command is `server /data --console-address ":9001"`, so the `command:` line can be left out. The `healthcheck:` block above only repeats the image's built-in health check with a shorter interval; it can also be left out.

The image tag is the server release tag. The client version is recorded in the `org.insectai.mc.tag` label.

## Creating buckets at startup

Set `MINIO_DEFAULT_BUCKETS` to a comma-separated list of buckets, each optionally followed by a colon and an anonymous-access policy:

```sh
MINIO_DEFAULT_BUCKETS=media:public,uploads:upload,backups
```

The policy is one of the values `mc anonymous set` accepts: `none`, `download` (anonymous read), `upload` (anonymous write) or `public` (anonymous read and write). A bucket without a policy is created private. The variable name and format are the same as in the Bitnami MinIO image, so this setting carries over from compose files written for that image. Bitnami's other variables (such as its port settings) are not supported.

When the variable is set, the entrypoint starts the server, waits until it is ready, creates each missing bucket, applies its policy, and logs one line per bucket. Existing buckets and their contents are left alone, and the policy is applied again on every start. If any step fails (an invalid bucket name, an unknown policy, wrong credentials), the entrypoint stops the server and the container exits with a non-zero status, so a misconfiguration is visible immediately instead of surfacing later as missing buckets. The server stays the container's main process: `docker stop` is forwarded to it and the container's exit code is the server's.

When the variable is not set, the entrypoint hands straight over to the server, so the image behaves exactly as it did before this feature existed. Any command other than `server ...` (for example `--version`) is passed to the `minio` binary unchanged, and `--entrypoint mc` still runs the client.

### Health check

The image declares a Docker `HEALTHCHECK` that runs `/usr/bin/minio-healthcheck`. It reports healthy only when the server answers its readiness probe and, if `MINIO_DEFAULT_BUCKETS` is set, the entrypoint has finished setting up the buckets (it writes a marker file, `/tmp/minio-default-buckets.ready`, as its last step). In compose, `depends_on: {minio: {condition: service_healthy}}` therefore means "the server is up and the buckets are in place", which replaces a separate init container.

### Limits

- The scripts talk to the server over plain HTTP on `127.0.0.1`. The API port is taken from the server's `--address` argument (for example `--address :9100`) and defaults to 9000. TLS on the API port is not supported by the bucket setup.
- The root credentials come from `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD`. Credentials supplied only through files or other mechanisms are not read.
- The entrypoint waits up to 120 seconds for the server to become ready before giving up. Set `MINIO_DEFAULT_BUCKETS_TIMEOUT` (in seconds) to change this.

## Bumping versions

1. Find the new release tags on [pgsty/silo/releases](https://github.com/pgsty/silo/releases) and [pgsty/mc/releases](https://github.com/pgsty/mc/releases), and read their release notes for behaviour changes.
Expand All @@ -60,10 +95,11 @@ The image tag is the server release tag. The client version is recorded in the `

```sh
./build.sh
docker run --rm insectai/minio:dev --version
docker run --rm --entrypoint mc insectai/minio:dev --version
test/smoke.sh insectai/minio:dev
```

The smoke test starts the image with and without `MINIO_DEFAULT_BUCKETS`, checks bucket creation, anonymous access, the health check, `--version` output and clean shutdown, and removes everything it created. The workflow runs the same script on every pull request.

5. Open a pull request. The workflow builds both platforms without pushing.
6. Merge to `main`. The workflow pushes `insectai/minio:<MINIO_TAG>` and `insectai/minio:latest`, and prints the digest pin line in the run summary.
7. Update the digest pins in the consuming repositories (for Antenna: `docker-compose.yml` and `docker-compose.ci.yml`).
Expand Down
117 changes: 117 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/bin/sh
# Container entrypoint: runs the MinIO server and, when MINIO_DEFAULT_BUCKETS is set, creates those
# buckets (and their anonymous-access policies) once the server is ready. This removes the need for a
# separate init container in compose stacks.
#
# MINIO_DEFAULT_BUCKETS="media:public,backups" # name[:policy], comma-separated
#
# The policy is one of mc's anonymous-access policies: none, download, upload, public. A bucket without a
# policy is created private. The variable name and format match the old Bitnami MinIO image.
#
# Anything other than `server ...` (for example `--version`) is passed straight to the minio binary.
# Without MINIO_DEFAULT_BUCKETS the server is exec'd directly, exactly as before this script existed.
# The API port is read from `--address` (default 9000) and shared with minio-healthcheck via a file.
set -eu

MARKER=/tmp/minio-default-buckets.ready
PORT_FILE=/tmp/minio-api-port
READY_TIMEOUT="${MINIO_DEFAULT_BUCKETS_TIMEOUT:-120}"

log() { echo "minio-default-buckets: $*"; }

if [ "${1:-}" != "server" ]; then
exec /usr/bin/minio "$@"
fi

# Find the API port from `--address HOST:PORT` or `--address=HOST:PORT`; the last occurrence wins.
address=""
prev=""
for arg in "$@"; do
case "$prev" in --address) address="$arg" ;; esac
case "$arg" in --address=*) address="${arg#--address=}" ;; esac
prev="$arg"
done
port=9000
if [ -n "$address" ]; then
port="${address##*:}"
fi
case "$port" in
'' | *[!0-9]*) echo "docker-entrypoint.sh: cannot read a port from --address '$address'" >&2; exit 64 ;;
esac

# A restarted container keeps /tmp, so clear the marker before the buckets are (re)checked.
rm -f "$MARKER"
echo "$port" > "$PORT_FILE"

if [ -z "${MINIO_DEFAULT_BUCKETS:-}" ]; then
exec /usr/bin/minio "$@"
fi

/usr/bin/minio "$@" &
server_pid=$!
trap 'kill -TERM "$server_pid" 2>/dev/null || true' TERM INT

# Returns the server's exit status once it has exited, surviving waits interrupted by a trapped signal.
wait_for_server() {
status=0
while :; do
wait "$server_pid" && status=0 || status=$?
kill -0 "$server_pid" 2>/dev/null || break
done
return "$status"
}

fail() {
echo "minio-default-buckets: $*; stopping the server" >&2
kill -TERM "$server_pid" 2>/dev/null || true
wait_for_server || true
exit 1
}

# A private mc config directory keeps credentials out of any ~/.mc the user may have mounted.
MC_CONFIG_DIR="$(mktemp -d /tmp/minio-entrypoint-mc.XXXXXX)"
export MC_CONFIG_DIR
endpoint="http://127.0.0.1:${port}"

# `mc ready` retries forever, so bound each attempt and check the server is still running between them.
elapsed=0
until MC_HOST_local="$endpoint" timeout 2 mc ready local >/dev/null 2>&1; do
if ! kill -0 "$server_pid" 2>/dev/null; then
wait_for_server && exit 0 || exit $?
fi
elapsed=$((elapsed + 3))
[ "$elapsed" -lt "$READY_TIMEOUT" ] || fail "server not ready after ${READY_TIMEOUT}s"
sleep 1
done

mc alias set local "$endpoint" "${MINIO_ROOT_USER:-minioadmin}" "${MINIO_ROOT_PASSWORD:-minioadmin}" >/dev/null \
|| fail "could not authenticate to the server"

old_ifs="$IFS"
IFS=,
for entry in $MINIO_DEFAULT_BUCKETS; do
IFS="$old_ifs"
entry="$(echo "$entry" | tr -d '[:space:]')"
[ -n "$entry" ] || continue
name="${entry%%:*}"
policy=""
case "$entry" in *:*) policy="${entry#*:}" ;; esac
case "$policy" in
'' | none | download | upload | public) ;;
*) fail "bucket '$name' has unknown policy '$policy' (use none, download, upload or public)" ;;
esac
mc mb --ignore-existing "local/$name" >/dev/null || fail "could not create bucket '$name'"
if [ -n "$policy" ]; then
mc anonymous set "$policy" "local/$name" >/dev/null || fail "could not set policy '$policy' on '$name'"
log "bucket '$name' ready (anonymous access: $policy)"
else
log "bucket '$name' ready"
fi
done
IFS="$old_ifs"

rm -rf "$MC_CONFIG_DIR"
touch "$MARKER"
log "all buckets ready"

wait_for_server && exit 0 || exit $?
17 changes: 17 additions & 0 deletions minio-healthcheck
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
# Health check for the image: succeeds only when the server answers its readiness probe and, if
# MINIO_DEFAULT_BUCKETS is set, the entrypoint has finished creating those buckets. Compose files can
# therefore use `condition: service_healthy` to mean "server up and buckets in place".
set -eu

port="$(cat /tmp/minio-api-port 2>/dev/null || echo 9000)"

# A throwaway config directory works whichever uid the container runs as.
export MC_CONFIG_DIR=/tmp/minio-healthcheck-mc

# `mc ready` retries forever on its own, so bound it; the health check runs again on the next interval.
MC_HOST_local="http://127.0.0.1:${port}" timeout 3 mc ready local >/dev/null 2>&1

if [ -n "${MINIO_DEFAULT_BUCKETS:-}" ] && [ ! -f /tmp/minio-default-buckets.ready ]; then
exit 1
fi
Loading
Loading