I know people groan at running infrastructure, but the registry software is really well documented and flexible.
If you don't need to 'push', but only pull - configuring them as pull through caches is nice for availability and reliability -- while also saving from nickle/diming.
They will get things from a configurable upstream, proxy.remoteurl.
Contrary to what the documentation says, this can work with anything speaking the API. Not just Dockerhub.
edit: My one criticism, it's not good from an HTTPS hardening perspective. It's functional, but audits find non-issues.
You'll want nginx or something in front to ensure good HSTS header coverage for non-actionable requests, for example.