Not OP, but to me the answer is:

  - gitea
  - woodpecker CI
  - my own docker registry
  - portainer running on my docker swarm

I then define the docker stack in the git repository, and CI builds the images and pushes to build the new image to the docker repository. The portainer API allows to deploy a stack, with the image tag as a parameter.