While changing the port mappings on the fly isn’t possible, you don’t need to update the docker image itself to expose ports. In fact the EXPOSE setting for an image is purely informational, it doesn’t actually “expose” anything. If you have a process listening on port 80, you’re free to start up a container with an external port number mapped to port 80 without ever adding port 80 as an EXPOSE’d port in the image.
If you wanted (for whatever reason) to startup tomcat on port 81, you could make a new image, using the original Tomcat image as a base image, and make customizations as you please. Want to make the container port assignable? Add an environment variable for that and make the tomcat server use that environment variable to set its port. It’s a contrived example since there shouldn’t be a need to change the port of the process, that’s what port mapping is for, but you get the point.
If you’re looking to add additional container processes, you can use the method above to add and startup the software, but be aware that this is an anti-pattern for docker images. Each container should have a single process running in it so that when the process terminates, so does the container. Otherwise you end up with init systems in which one process might die and then you could end up with a container that is running but not fully functional. There are exceptions for sure, but they should be just that.