All docker does is look at the string you run, along with the environment you pass in, and compare it to other images in the build cache. If you have a Dockerfile that doesn't pin a package, like:
RUN apt-get update && apt-get install -y \
package-foo
and you first run this when version 1.2 is the current latest, and then run the same command again after 1.3 gets released, docker will see the command is identical, and reuse the build cache from the previous build of the image, rather than pulling the newer version of that package.
If instead you specify a version like:
RUN apt-get update && apt-get install -y \
package-foo=1.2.*
And then rebuild with an updated Dockerfile containing a different version pinned:
RUN apt-get update && apt-get install -y \
package-foo=1.3.*
The second build will be a different command to run, and therefore force docker to rerun the build without the cache from the previous run. This also has the advantage that a 1.4 release doesn't get pulled in unexpectedly (e.g. if an earlier line breaks the cache or the cache gets removed/disabled).
If you just want the most recent versions of these packages, regardless of the cache, then you can skip version pinning and either intentionally break the cache or disable the cache. To disable the cache, you can build with the flag --no-cache. To intentionally break the cache, you can pass a changing build arg with something like:
docker build --build-arg "TIMESTAMP=$(date +%s)" .
And a Dockerfile that defines that build arg before you want to break the cache:
ARG TIMESTAMP
RUN apt-get update && apt-get install -y \
package-foo