User defined networks already provide a built-in dns-based service discovery. The service can either use endpoint_mode: vip (which is the default) to make the dns entry resolve to the service vip, or endpoint_mode: dnsrr to resolve it to a multivalued response with the ips of each service task (=container), see: Compose file version 3 reference | Docker Docs. Even with endpoint_mode: vip, the multivalued response of the container ips can be queried using tasks.{servicename}.
If this doesn’t help in your situation, then you could use template placeholders with hostname to make the task slot (~=the replica id) part of the hostname:
Template placeholders can also be used in environments or volumes.