Personally, I think of DNA as being like the Linux kernel codebase. There's a lot of raw "code" there — but not all of it ends up in every running kernel!
DNA methyl-groups, acetylated histones, etc — these are the config file generated by running `menuconfig`. They determine:
1. which components of the kernel the "compiler toolchain" (RNA polymerase) will actually "compile" (transcribe) into kernel-module objects (RNA sequences, proteins) — in turn determining which "features" (developmental or metabolic processes) each of the components (cell/tissue types) of the resulting system (organism) will have; and
2. which #IFDEFs (coding regions) within those modules will actually be macro-enabled (expressed) during compile-time (transcription) — in turn influencing / varying / tuning the strategies and logic (frequency of production / likelihood of expression, final shape / folding / binding affinities of proteins) used by any given component (cell/tissue type) to perform a given "feature" (developmental or metabolic process).
(And just a tweak to steer this intuition pump — your body doesn't have one kernel, compiled once at conception; rather, your body is more like a distributed system composed of machines running Gentoo — every cell has its own kernel, and each cell is "tweaking" and "recompiling" its kernel regularly. When cells undergo mitosis, both daughter cells inherit these tweaks — that's like setting up a new one of these machines by mirroring the hard drive from an existing machine, carrying over not just the kernel but also the at-snapshot version of the `menuconfig` configuration file.)
The Linux kernel is just a much closer analogy than your average piece of software would be, because the Linux kernel actually has this distinct step that generates a standalone build config file that later build steps read.
Most regular software build infrastructure (autoconf, cmake, etc) just directly "burns in" any passed-in build config as generated code, without any intermediary file storing the config itself — which is a very different and not-very-analogous process. (That'd be more like if our epigenome was an extra chromosome of DNA built up during development!)
The gene itself determines the shape and character of a protein; Gene expression refers to the quantity of a protein produced. Changes to the genome are long lasting and somewhat rare inside a living person, while changes to the epigenome happen fairly regularly.
A good way to think of this is cell differentiation. Every cell in your body, except eggs and sperm, contains a copy of your entire genome but what differentiates a nerve cell from a liver cell from a skin cell is gene expression. All it takes is some changes to the epigenome of a stem cell during mitosis for one the resulting cells to become a new, differentiated cell.
Imagine you have the text of a book in a word processor. You can change the text by typing new words or deleting ones that are there. You can also change the font, size, alignment, etc. The latter category of changes does not alter the words in the text, but it can affect how that text is interpreted, which parts of the text a reader focuses on, etc. The difference between a genetic alteration and an epigenetic alteration is conceptually similar. Genetics is changing the "text" of the genome while epigenetics is changing aspects of the genome that affect how that "text" is interpreted and used.
DNA is source code, and there is a bunch of RNA processes that read it and do stuff to make you live. Parts of DNA can be turned on and off, that’s called expression. Epigenetics is the study of how genes are expressed. Gene expression can change without the genome itself changing depending on external conditions, which is a key part of adaptability.