L'ENTRYPOINT sera toujours exécuté (sauf si tu l'overwrite avec --entrypoint), et CMD devient donc un paramètre de l'ENTRYPOINT.
Donc si tu lances docker run -ti onyx sh, ton conteneur lance en réalité ./start.sh sh, sh n'est qu'un paramètre. Après dans le start.sh, $# permets de compter le nombre de paramètre, si y'a pas de paramètre, il exécute python3 manage.py runserver -h 0.0.0.0 -p 5080 -d -r (ce qui ne devrait jamais arrivé, puisque CMD est écris dans le Dockerfile), si c'est pas vide, il exécute $@, qui est tout les paramètres passé à start.sh, dans notre exemple sh.
Comme quand j'ai lancé docker run -ti onyx ls -l, le conteneur lance start.sh ls -l, donc il exécute tout le start.sh, puis après ls -l, $@ == ls -l donc $# == 2.
L'avantage étant aussi que le CMD sera exécuté avec les droits que tu as indiqué dans le start.sh.
Je sais pas si j'ai été très clair ^^.