docker-composeでportsとexposeの使い分けを整理します.
exposeはホストからアクセスできないポートですが,portsはホストからアクセスできるポートです.*1
例えば,port 5432にpostgres データベースを起動し,djangoを使ってサーバーを作り,ブラウザーからアクセスするという開発環境を考えます.
version: '3'
services:
  postgres:
    image: postgres:11-alpine
    expose:
      - "5432"
  django:
    image: django-web:latest
    command: ash -c "
      python3 manage.py migrate && 
      python3 manage.py runserver 0.0.0.0:8000"
    ports:
      - "8000:8000"
postgresはdjangoからアクセスできればよいので,exposeを使用します.
djangoはホストのブラウザーからアクセスするので,portsを使用します.
ホストのポートを指定するのは必要がなければ,docker-composeに一任するのがよいです.例えば,ports: "5432:5432" と指定してpostgresを起動すると,複数のpostgresを使いたい時に,ポートで競合してエラーとなります.
Bind for 0.0.0.0:5432 failed: port is already allocated.
指定しなければdocker-composeが自動的に空いているアドレスやポートを設定してくれるので,便利です.