The layers are inherited from the base image, whatever that image contains will be the starting point for your new image.
Those saying that your example will result in 4 layers are correct because ubuntu only has one layer to start with, not because the FROM line is repackaged into a single layer.
$ docker inspect ubuntu:latest --format '{{json .RootFS.Layers}}' | jq .
[
"sha256:b8a36d10656ac19ddb96ef3107f76820663717708fc37ce929925c36d1b1d157"
]
Lets take a better example, nginx contains several layers:
$ cat df.layers
FROM nginx:latest
RUN echo "hello" >/hello.txt
CMD [ "ls", "-l", "/" ]
$ docker build -t test-layers -f df.layers .
[+] Building 0.9s (6/6) FINISHED
=> [internal] load build definition from df.layers 0.0s
=> => transferring dockerfile: 107B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 49B 0.0s
=> [internal] load metadata for docker.io/library/nginx:latest 0.0s
=> [1/2] FROM docker.io/library/nginx:latest 0.1s
=> [2/2] RUN echo "hello" >/hello.txt 0.7s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:3bc381601cdb33e4159ac4ee7e3e5724dbd47b01e37c24f2aac0dc1fbd40131e 0.0s
=> => naming to docker.io/library/test-layers 0.0s
Next, lets compare the resulting layers in each of those images:
$ docker inspect nginx:latest --format '{{json .RootFS.Layers}}' | jq .
[
"sha256:8553b91047dad45bedc292812586f1621e0a464a09a7a7c2ce6ac5f8ba2535d7",
"sha256:a29cc9587af6488ae0cbb962ecbe023d347908cc62ca5d715af06e54ccaa9e36",
"sha256:6bc8ae8fb3cf0909b3d9c2e74f6cabe16e6a2322c52cec76fbaecaef47006f1d",
"sha256:5684be535bf11cb9ad1a57b51085f36d84ae8361eabc2b4c2ba9a83e8b084b20",
"sha256:93ee76f39c974e4f819e632149c002d6f509aadc5995ec6523a96b337751c8ed",
"sha256:1040838fe30e6f26d31bde96c514f47ee4bf727b3f1c3c7b045ea3891c1c2150"
]
$ docker inspect test-layers:latest --format '{{json .RootFS.Layers}}' | jq .
[
"sha256:8553b91047dad45bedc292812586f1621e0a464a09a7a7c2ce6ac5f8ba2535d7",
"sha256:a29cc9587af6488ae0cbb962ecbe023d347908cc62ca5d715af06e54ccaa9e36",
"sha256:6bc8ae8fb3cf0909b3d9c2e74f6cabe16e6a2322c52cec76fbaecaef47006f1d",
"sha256:5684be535bf11cb9ad1a57b51085f36d84ae8361eabc2b4c2ba9a83e8b084b20",
"sha256:93ee76f39c974e4f819e632149c002d6f509aadc5995ec6523a96b337751c8ed",
"sha256:1040838fe30e6f26d31bde96c514f47ee4bf727b3f1c3c7b045ea3891c1c2150",
"sha256:6994e46eed98d24824300283a52d7e6c905936c688366c51a77ab27c2f7b80e4"
]
The first 6 layers are identical, then the RUN in the Dockerfile added a single new layer. These layers are shared since they are part of a content addressable store, it's a hash of the tar+gz of the filesystem changes. They don't get repacked into a single layer which would result in more space to store and bandwidth to distribute.
You can also see the steps in the history of the image, not every step creates a layer, and docker shows these in reverse chronological order:
$ docker history test-layers
IMAGE CREATED CREATED BY SIZE COMMENT
3bc381601cdb 2 minutes ago CMD ["ls" "-l" "/"] 0B buildkit.dockerfile.v0
<missing> 2 minutes ago RUN /bin/sh -c echo "hello" >/hello.txt # bu… 6B buildkit.dockerfile.v0
<missing> 5 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 5 days ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 5 days ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 5 days ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 5 days ago /bin/sh -c #(nop) COPY file:e57eef017a414ca7… 4.62kB
<missing> 5 days ago /bin/sh -c #(nop) COPY file:abbcbf84dc17ee44… 1.27kB
<missing> 5 days ago /bin/sh -c #(nop) COPY file:5c18272734349488… 2.12kB
<missing> 5 days ago /bin/sh -c #(nop) COPY file:7b307b62e82255f0… 1.62kB
<missing> 5 days ago /bin/sh -c set -x && addgroup --system -… 61.6MB
<missing> 5 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 0B
<missing> 5 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.11 0B
<missing> 5 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.23.4 0B
<missing> 5 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 5 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 5 days ago /bin/sh -c #(nop) ADD file:a2378c1b12e95db69… 80.5MB