I had Portainer setup, but it was clunky and the web UI added little value.
Now I just have a local git repo with a directory for each compose stack and run docker compose commands as needed. The repo holds all yaml and config files I care to keep track. Env variables are in gitignored .env files with similar .env.example in version control. I keep sensitive info in my password manager if I have to recreate a .env from its example counterpart.
To handle volumes, I avoid docker-managed volumes at all costs to favor cleaner bind mounts instead. This way the data for each stack is always along with the corresponding configuration files. If I care about keeping the data, it's either version controlled (when mostly text) or backed up with kopia (when mostly binary).
I do something similar, but I avoid gitignore at all costs because any secret data should have root read only permissions on it. Plus any data that is not version controlled goes in a common directory, so all I have to do is backup that directory and I'm good. It makes moving between machines easy if I ever need to do that.