No need to create a database in advance (they're created on the fly), but it makes sense to create a non-root user the first time the container starts.
docker-compose.yml:
services:
  ...
  mongo:
    image: mongo:6-jammy
    env_file: .env-mongo
    volumes:
      - mongo:/data/db
      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js
      # - ./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh  # mongo < 6
volumes:
  mongo:
init-mongo.sh doesn't need to be executable. It's sourced.
.env (app's variables):
MONGO_USER=user
MONGO_PASSWORD=userpasswd
MONGO_DB=foo
.env-mongo (mongo service's variables):
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=rootpasswd
MONGO_INITDB_DATABASE=foo  # for mongo >= 6
Since mongo-6 one can use process.env, so use init-mongo.js. Before that, use init-mongo.sh (change the script that you attach in docker-compose.yml, and MONGO_INITDB_DATABASE is not needed in this case).
.env is meant to be used by the app service(s), .env-mongo by the mongo service.
MONGO_INITDB_* variables are used by the mongo's entrypoint. The docs can be found on Docker Hub. Also it's the entrypoint that runs the initialization script.
init-mongo.js:
db.getSiblingDB('admin').auth(
    process.env.MONGO_INITDB_ROOT_USERNAME,
    process.env.MONGO_INITDB_ROOT_PASSWORD
);
db.createUser({
    user: process.env.MONGO_USER,
    pwd: process.env.MONGO_PASSWORD,
    roles: ["readWrite"],
});
init-mongo.sh:
q_MONGO_USER=`jq --arg v "$MONGO_USER" -n '$v'`
q_MONGO_PASSWORD=`jq --arg v "$MONGO_PASSWORD" -n '$v'`
mongo -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" admin <<EOF
    use foo;
    db.createUser({
        user: $q_MONGO_USER,
        pwd: $q_MONGO_PASSWORD,
        roles: ["readWrite"],
    });
EOF
More files in a gist.