diff --git a/data/ubuntu-distro-info.json b/data/ubuntu-distro-info.json
new file mode 100644
index 0000000000000000000000000000000000000000..75d602ec51f7809b01d2409b2c004979636b6599
--- /dev/null
+++ b/data/ubuntu-distro-info.json
@@ -0,0 +1,38 @@
+{
+  "4.10": "warty",
+  "5.04": "hoary",
+  "5.10": "breezy",
+  "6.06": "dapper",
+  "6.10": "edgy",
+  "7.04": "feisty",
+  "7.10": "gutsy",
+  "8.04": "hardy",
+  "8.10": "intrepid",
+  "9.04": "jaunty",
+  "9.10": "karmic",
+  "10.04": "lucid",
+  "10.10": "maverick",
+  "11.04": "natty",
+  "11.10": "oneiric",
+  "12.04": "precise",
+  "12.10": "quantal",
+  "13.04": "raring",
+  "13.10": "saucy",
+  "14.04": "trusty",
+  "14.10": "utopic",
+  "15.04": "vivid",
+  "15.10": "wily",
+  "16.04": "xenial",
+  "16.10": "yakkety",
+  "17.04": "zesty",
+  "17.10": "artful",
+  "18.04": "bionic",
+  "18.10": "cosmic",
+  "19.04": "disco",
+  "19.10": "eoan",
+  "20.04": "focal",
+  "20.10": "groovy",
+  "21.04": "hirsute",
+  "21.10": "impish",
+  "22.04": "jammy"
+}
diff --git a/docs/usage/docker.md b/docs/usage/docker.md
index d7632eb12488319d9684501b303d8224383f1a22..da6fd8dc3fcb25f7efebb3d4f4076dff3f283bf1 100644
--- a/docs/usage/docker.md
+++ b/docs/usage/docker.md
@@ -117,6 +117,27 @@ If you wish to enable major versions then add the preset `docker:enableMajor` to
 Renovate has some Docker-specific intelligence when it comes to versions.
 For example:
 
+### Ubuntu codenames
+
+Renovate understands [Ubuntu release code names](https://wiki.ubuntu.com/Releases) and will offer upgrades to the latest LTS release (e.g. from `ubuntu:xenial` to `ubuntu:focal`).
+
+For this to work you must follow this naming scheme:
+
+- The first term of the full codename is used (e.g. `bionic` for `Bionic Beaver` release)
+- The codename is in lowercase
+
+For example, Renovate will offer to upgrade the following `Dockerfile` layer:
+
+```dockerfile
+FROM ubuntu:yakkety
+```
+
+To
+
+```dockerfile
+FROM ubuntu:focal
+```
+
 ## Configuring/Disabling
 
 If you wish to make changes that apply to all Docker managers, then add them to the `docker` config object.
diff --git a/lib/modules/versioning/ubuntu/distribution.ts b/lib/modules/versioning/ubuntu/distribution.ts
new file mode 100644
index 0000000000000000000000000000000000000000..999e3c382c4934ea98215a5737df9d03e35bab93
--- /dev/null
+++ b/lib/modules/versioning/ubuntu/distribution.ts
@@ -0,0 +1,40 @@
+import dataFiles from '../../../data-files.generated';
+
+export type UbuntuDistroInfo = Record<string, string>;
+
+// Data file generated with:
+// distro-json-generate.mjs
+const ubuntuJsonKey = 'data/ubuntu-distro-info.json';
+
+const ubuntuDistroInfo: UbuntuDistroInfo = JSON.parse(
+  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+  dataFiles.get(ubuntuJsonKey)!
+);
+
+const codenameToVersion = new Map<string, string>();
+
+for (const version of Object.keys(ubuntuDistroInfo)) {
+  const codename = ubuntuDistroInfo[version];
+  codenameToVersion.set(codename, version);
+}
+
+export function isCodename(input: string): boolean {
+  return codenameToVersion.has(input);
+}
+
+export function getVersionByCodename(input: string): string {
+  const ver = codenameToVersion.get(input);
+  if (ver) {
+    return ver;
+  }
+  return input;
+}
+
+export function getCodenameByVersion(input: string): string {
+  const codename = ubuntuDistroInfo[input];
+  if (codename) {
+    return codename;
+  }
+  // istanbul ignore next
+  return input;
+}
diff --git a/lib/modules/versioning/ubuntu/index.spec.ts b/lib/modules/versioning/ubuntu/index.spec.ts
index c3e65b5abb93e6325f02698403b156e6e95fd4cf..1d286adfabc33bffeba6d73aa31adebe707adcd3 100644
--- a/lib/modules/versioning/ubuntu/index.spec.ts
+++ b/lib/modules/versioning/ubuntu/index.spec.ts
@@ -2,45 +2,82 @@ import { api as ubuntu } from '.';
 
 describe('modules/versioning/ubuntu/index', () => {
   test.each`
-    version      | expected
-    ${undefined} | ${false}
-    ${null}      | ${false}
-    ${''}        | ${false}
-    ${'xenial'}  | ${false}
-    ${'04.10'}   | ${true}
-    ${'05.04'}   | ${true}
-    ${'05.10'}   | ${true}
-    ${'6.06'}    | ${true}
-    ${'6.10'}    | ${true}
-    ${'7.04'}    | ${true}
-    ${'7.10'}    | ${true}
-    ${'8.04'}    | ${true}
-    ${'8.10'}    | ${true}
-    ${'9.04'}    | ${true}
-    ${'9.10'}    | ${true}
-    ${'10.04.4'} | ${true}
-    ${'10.10'}   | ${true}
-    ${'11.04'}   | ${true}
-    ${'11.10'}   | ${true}
-    ${'12.04.5'} | ${true}
-    ${'12.10'}   | ${true}
-    ${'13.04'}   | ${true}
-    ${'13.10'}   | ${true}
-    ${'14.04.6'} | ${true}
-    ${'14.10'}   | ${true}
-    ${'15.04'}   | ${true}
-    ${'15.10'}   | ${true}
-    ${'16.04.7'} | ${true}
-    ${'16.10'}   | ${true}
-    ${'17.04'}   | ${true}
-    ${'17.10'}   | ${true}
-    ${'18.04.5'} | ${true}
-    ${'18.10'}   | ${true}
-    ${'19.04'}   | ${true}
-    ${'19.10'}   | ${true}
-    ${'20.04'}   | ${true}
-    ${'20.10'}   | ${true}
-    ${'2020.04'} | ${false}
+    version        | expected
+    ${undefined}   | ${false}
+    ${null}        | ${false}
+    ${''}          | ${false}
+    ${'xenial'}    | ${true}
+    ${'04.10'}     | ${true}
+    ${'05.04'}     | ${true}
+    ${'05.10'}     | ${true}
+    ${'6.06'}      | ${true}
+    ${'6.10'}      | ${true}
+    ${'7.04'}      | ${true}
+    ${'7.10'}      | ${true}
+    ${'8.04'}      | ${true}
+    ${'8.10'}      | ${true}
+    ${'9.04'}      | ${true}
+    ${'9.10'}      | ${true}
+    ${'10.04.4'}   | ${true}
+    ${'10.10'}     | ${true}
+    ${'11.04'}     | ${true}
+    ${'11.10'}     | ${true}
+    ${'12.04.5'}   | ${true}
+    ${'12.10'}     | ${true}
+    ${'13.04'}     | ${true}
+    ${'13.10'}     | ${true}
+    ${'14.04.6'}   | ${true}
+    ${'14.10'}     | ${true}
+    ${'15.04'}     | ${true}
+    ${'15.10'}     | ${true}
+    ${'16.04.7'}   | ${true}
+    ${'16.10'}     | ${true}
+    ${'17.04'}     | ${true}
+    ${'17.10'}     | ${true}
+    ${'18.04.5'}   | ${true}
+    ${'18.10'}     | ${true}
+    ${'19.04'}     | ${true}
+    ${'19.10'}     | ${true}
+    ${'20.04'}     | ${true}
+    ${'20.10'}     | ${true}
+    ${'2020.04'}   | ${false}
+    ${'xenial'}    | ${true}
+    ${'warty'}     | ${true}
+    ${'hoary'}     | ${true}
+    ${'breezy'}    | ${true}
+    ${'dapper'}    | ${true}
+    ${'edgy'}      | ${true}
+    ${'feisty'}    | ${true}
+    ${'gutsy'}     | ${true}
+    ${'hardy'}     | ${true}
+    ${'intrepid'}  | ${true}
+    ${'jaunty'}    | ${true}
+    ${'karmic'}    | ${true}
+    ${'lucid.4'}   | ${false}
+    ${'maverick'}  | ${true}
+    ${'natty'}     | ${true}
+    ${'oneiric'}   | ${true}
+    ${'precise.5'} | ${false}
+    ${'quantal'}   | ${true}
+    ${'raring'}    | ${true}
+    ${'saucy'}     | ${true}
+    ${'trusty.6'}  | ${false}
+    ${'utopic'}    | ${true}
+    ${'vivid'}     | ${true}
+    ${'wily'}      | ${true}
+    ${'xenial.7'}  | ${false}
+    ${'yakkety'}   | ${true}
+    ${'zesty'}     | ${true}
+    ${'artful'}    | ${true}
+    ${'bionic.5'}  | ${false}
+    ${'cosmic'}    | ${true}
+    ${'disco'}     | ${true}
+    ${'eoan'}      | ${true}
+    ${'focal'}     | ${true}
+    ${'groovy'}    | ${true}
+    ${'hirsute'}   | ${true}
+    ${'impish'}    | ${true}
+    ${'jammy'}     | ${true}
   `('isValid("$version") === $expected', ({ version, expected }) => {
     expect(!!ubuntu.isValid(version)).toBe(expected);
   });
@@ -52,6 +89,8 @@ describe('modules/versioning/ubuntu/index', () => {
     ${''}        | ${undefined} | ${false}
     ${'04.10'}   | ${undefined} | ${true}
     ${'20.10'}   | ${undefined} | ${true}
+    ${'warty'}   | ${undefined} | ${true}
+    ${'groovy'}  | ${undefined} | ${true}
   `(
     'isCompatible("$version") === $expected',
     ({ version, range, expected }) => {
@@ -72,56 +111,92 @@ describe('modules/versioning/ubuntu/index', () => {
   });
 
   test.each`
-    version      | expected
-    ${undefined} | ${false}
-    ${null}      | ${false}
-    ${''}        | ${false}
-    ${'04.10'}   | ${false}
-    ${'05.04'}   | ${false}
-    ${'05.10'}   | ${false}
-    ${'6.06'}    | ${false}
-    ${'6.10'}    | ${false}
-    ${'7.04'}    | ${false}
-    ${'7.10'}    | ${false}
-    ${'8.04'}    | ${true}
-    ${'8.10'}    | ${false}
-    ${'9.04'}    | ${false}
-    ${'9.10'}    | ${false}
-    ${'10.04.4'} | ${true}
-    ${'10.10'}   | ${false}
-    ${'11.04'}   | ${false}
-    ${'11.10'}   | ${false}
-    ${'12.04.5'} | ${true}
-    ${'12.10'}   | ${false}
-    ${'13.04'}   | ${false}
-    ${'13.10'}   | ${false}
-    ${'14.04.6'} | ${true}
-    ${'14.10'}   | ${false}
-    ${'15.04'}   | ${false}
-    ${'15.10'}   | ${false}
-    ${'16.04.7'} | ${true}
-    ${'16.10'}   | ${false}
-    ${'17.04'}   | ${false}
-    ${'17.10'}   | ${false}
-    ${'18.04.5'} | ${true}
-    ${'18.10'}   | ${false}
-    ${'19.04'}   | ${false}
-    ${'19.10'}   | ${false}
-    ${'20.04'}   | ${true}
-    ${'20.10'}   | ${false}
-    ${'42.01'}   | ${false}
-    ${'42.02'}   | ${false}
-    ${'42.03'}   | ${false}
-    ${'42.04'}   | ${true}
-    ${'42.05'}   | ${false}
-    ${'42.06'}   | ${false}
-    ${'42.07'}   | ${false}
-    ${'42.08'}   | ${false}
-    ${'42.09'}   | ${false}
-    ${'42.10'}   | ${false}
-    ${'42.11'}   | ${false}
-    ${'2020.04'} | ${false}
-    ${'22.04'}   | ${false}
+    version       | expected
+    ${undefined}  | ${false}
+    ${null}       | ${false}
+    ${''}         | ${false}
+    ${'04.10'}    | ${false}
+    ${'05.04'}    | ${false}
+    ${'05.10'}    | ${false}
+    ${'6.06'}     | ${false}
+    ${'6.10'}     | ${false}
+    ${'7.04'}     | ${false}
+    ${'7.10'}     | ${false}
+    ${'8.04'}     | ${true}
+    ${'8.10'}     | ${false}
+    ${'9.04'}     | ${false}
+    ${'9.10'}     | ${false}
+    ${'10.04.4'}  | ${true}
+    ${'10.10'}    | ${false}
+    ${'11.04'}    | ${false}
+    ${'11.10'}    | ${false}
+    ${'12.04.5'}  | ${true}
+    ${'12.10'}    | ${false}
+    ${'13.04'}    | ${false}
+    ${'13.10'}    | ${false}
+    ${'14.04.6'}  | ${true}
+    ${'14.10'}    | ${false}
+    ${'15.04'}    | ${false}
+    ${'15.10'}    | ${false}
+    ${'16.04.7'}  | ${true}
+    ${'16.10'}    | ${false}
+    ${'17.04'}    | ${false}
+    ${'17.10'}    | ${false}
+    ${'18.04.5'}  | ${true}
+    ${'18.10'}    | ${false}
+    ${'19.04'}    | ${false}
+    ${'19.10'}    | ${false}
+    ${'20.04'}    | ${true}
+    ${'20.10'}    | ${false}
+    ${'42.01'}    | ${false}
+    ${'42.02'}    | ${false}
+    ${'42.03'}    | ${false}
+    ${'42.04'}    | ${true}
+    ${'42.05'}    | ${false}
+    ${'42.06'}    | ${false}
+    ${'42.07'}    | ${false}
+    ${'42.08'}    | ${false}
+    ${'42.09'}    | ${false}
+    ${'42.10'}    | ${false}
+    ${'42.11'}    | ${false}
+    ${'2020.04'}  | ${false}
+    ${'22.04'}    | ${false}
+    ${'warty'}    | ${false}
+    ${'hoary'}    | ${false}
+    ${'breezy'}   | ${false}
+    ${'dapper'}   | ${false}
+    ${'edgy'}     | ${false}
+    ${'feisty'}   | ${false}
+    ${'gutsy'}    | ${false}
+    ${'hardy'}    | ${true}
+    ${'intrepid'} | ${false}
+    ${'jaunty'}   | ${false}
+    ${'karmic'}   | ${false}
+    ${'lucid'}    | ${true}
+    ${'maverick'} | ${false}
+    ${'natty'}    | ${false}
+    ${'oneiric'}  | ${false}
+    ${'precise'}  | ${true}
+    ${'quantal'}  | ${false}
+    ${'raring'}   | ${false}
+    ${'saucy'}    | ${false}
+    ${'trusty'}   | ${true}
+    ${'utopic'}   | ${false}
+    ${'vivid'}    | ${false}
+    ${'wily'}     | ${false}
+    ${'xenial'}   | ${true}
+    ${'yakkety'}  | ${false}
+    ${'zesty'}    | ${false}
+    ${'artful'}   | ${false}
+    ${'bionic'}   | ${true}
+    ${'cosmic'}   | ${false}
+    ${'disco'}    | ${false}
+    ${'eoan'}     | ${false}
+    ${'focal'}    | ${true}
+    ${'groovy'}   | ${false}
+    ${'hirsute'}  | ${false}
+    ${'impish'}   | ${false}
+    ${'jammy'}    | ${false}
   `('isStable("$version") === $expected', ({ version, expected }) => {
     const res = !!ubuntu.isStable(version);
     expect(res).toBe(expected);
@@ -152,20 +227,47 @@ describe('modules/versioning/ubuntu/index', () => {
     ${'20.10'}   | ${true}
     ${'30.11'}   | ${true}
     ${'2020.04'} | ${false}
+    ${'warty'}   | ${true}
+    ${'hoary'}   | ${true}
+    ${'dapper'}  | ${true}
+    ${'hardy'}   | ${true}
+    ${'jaunty'}  | ${true}
+    ${'lucid'}   | ${true}
+    ${'precise'} | ${true}
+    ${'raring'}  | ${true}
+    ${'trusty'}  | ${true}
+    ${'vivid'}   | ${true}
+    ${'xenial'}  | ${true}
+    ${'yakkety'} | ${true}
+    ${'zesty'}   | ${true}
+    ${'bionic'}  | ${true}
+    ${'cosmic'}  | ${true}
+    ${'focal'}   | ${true}
+    ${'groovy'}  | ${true}
+    ${'hirsute'} | ${true}
+    ${'impish'}  | ${true}
+    ${'jammy'}   | ${true}
+    ${'Groovy'}  | ${false}
+    ${'Hirsute'} | ${false}
+    ${'impish-'} | ${false}
+    ${'JAMMY'}   | ${false}
   `('isVersion("$version") === $expected', ({ version, expected }) => {
     expect(!!ubuntu.isVersion(version)).toBe(expected);
   });
 
   test.each`
-    version      | major   | minor   | patch
-    ${undefined} | ${null} | ${null} | ${null}
-    ${null}      | ${null} | ${null} | ${null}
-    ${''}        | ${null} | ${null} | ${null}
-    ${'42'}      | ${null} | ${null} | ${null}
-    ${'2020.04'} | ${null} | ${null} | ${null}
-    ${'04.10'}   | ${4}    | ${10}   | ${null}
-    ${'18.04.5'} | ${18}   | ${4}    | ${5}
-    ${'20.04'}   | ${20}   | ${4}    | ${null}
+    version       | major   | minor   | patch
+    ${undefined}  | ${null} | ${null} | ${null}
+    ${null}       | ${null} | ${null} | ${null}
+    ${''}         | ${null} | ${null} | ${null}
+    ${'42'}       | ${null} | ${null} | ${null}
+    ${'2020.04'}  | ${null} | ${null} | ${null}
+    ${'04.10'}    | ${4}    | ${10}   | ${null}
+    ${'18.04.5'}  | ${18}   | ${4}    | ${5}
+    ${'20.04'}    | ${20}   | ${4}    | ${null}
+    ${'intrepid'} | ${8}    | ${10}   | ${null}
+    ${'bionic'}   | ${18}   | ${4}    | ${null}
+    ${'focal'}    | ${20}   | ${4}    | ${null}
   `(
     'getMajor, getMinor, getPatch for "$version"',
     ({ version, major, minor, patch }) => {
@@ -176,11 +278,16 @@ describe('modules/versioning/ubuntu/index', () => {
   );
 
   test.each`
-    a          | b            | expected
-    ${'20.04'} | ${'2020.04'} | ${false}
-    ${'focal'} | ${'20.04'}   | ${false}
-    ${'20.04'} | ${'focal'}   | ${false}
-    ${'19.10'} | ${'19.10'}   | ${true}
+    a           | b            | expected
+    ${'20.04'}  | ${'2020.04'} | ${false}
+    ${'17.10'}  | ${'artful'}  | ${true}
+    ${'xenial'} | ${'artful'}  | ${false}
+    ${'17.04'}  | ${'artful'}  | ${false}
+    ${'artful'} | ${'17.10'}   | ${true}
+    ${'16.04'}  | ${'xenial'}  | ${true}
+    ${'focal'}  | ${'20.04'}   | ${true}
+    ${'20.04'}  | ${'focal'}   | ${true}
+    ${'19.10'}  | ${'19.10'}   | ${true}
   `('equals($a, $b) === $expected', ({ a, b, expected }) => {
     expect(ubuntu.equals(a, b)).toBe(expected);
   });
@@ -198,6 +305,16 @@ describe('modules/versioning/ubuntu/index', () => {
     ${'19.10.1'} | ${'20.04.1'} | ${false}
     ${'20.04.1'} | ${'19.10.1'} | ${true}
     ${'xxx'}     | ${'yyy'}     | ${false}
+    ${'focal'}   | ${'groovy'}  | ${false}
+    ${'groovy'}  | ${'focal'}   | ${true}
+    ${'eoan'}    | ${'focal'}   | ${false}
+    ${'focal'}   | ${'eoan'}    | ${true}
+    ${'vivid'}   | ${'saucy'}   | ${true}
+    ${'impish'}  | ${'focal'}   | ${true}
+    ${'eoan'}    | ${'quantal'} | ${true}
+    ${'focal'}   | ${'lucid'}   | ${true}
+    ${'eoan'}    | ${'focal'}   | ${false}
+    ${'focal'}   | ${'eoan'}    | ${true}
   `('isGreaterThan("$a", "$b") === $expected', ({ a, b, expected }) => {
     expect(ubuntu.isGreaterThan(a, b)).toBe(expected);
   });
@@ -209,6 +326,11 @@ describe('modules/versioning/ubuntu/index', () => {
     ${['18.10', '19.04', '19.10', '20.04']} | ${'20.04'}   | ${'20.04'}
     ${['18.10', '19.04', '19.10', '20.04']} | ${'19.10'}   | ${'19.10'}
     ${['18.10', '19.04', '19.10', '20.04']} | ${'04.10'}   | ${null}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'2020.04'} | ${null}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'foobar'}  | ${null}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'focal'}   | ${'focal'}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'eoan'}    | ${'eoan'}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'warty'}   | ${null}
   `(
     'getSatisfyingVersion($versions, "$range") === "$expected"',
     ({ versions, range, expected }) => {
@@ -223,6 +345,11 @@ describe('modules/versioning/ubuntu/index', () => {
     ${['18.10', '19.04', '19.10', '20.04']} | ${'20.04'}   | ${'20.04'}
     ${['18.10', '19.04', '19.10', '20.04']} | ${'19.10'}   | ${'19.10'}
     ${['18.10', '19.04', '19.10', '20.04']} | ${'04.10'}   | ${null}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'2020.04'} | ${null}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'foobar'}  | ${null}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'focal'}   | ${'focal'}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'eoan'}    | ${'eoan'}
+    ${['cosmic', 'disco', 'eoan', 'focal']} | ${'warty'}   | ${null}
   `(
     'minSatisfyingVersion($versions, "$range") === "$expected"',
     ({ versions, range, expected }) => {
@@ -233,6 +360,10 @@ describe('modules/versioning/ubuntu/index', () => {
   test.each`
     currentValue | rangeStrategy | currentVersion | newVersion  | expected
     ${undefined} | ${undefined}  | ${undefined}   | ${'foobar'} | ${'foobar'}
+    ${'xenial'}  | ${undefined}  | ${undefined}   | ${'20.04'}  | ${'focal'}
+    ${'xenial'}  | ${undefined}  | ${undefined}   | ${'focal'}  | ${'focal'}
+    ${'16.04'}   | ${undefined}  | ${undefined}   | ${'20.04'}  | ${'20.04'}
+    ${'16.04'}   | ${undefined}  | ${undefined}   | ${'focal'}  | ${'20.04'}
   `(
     'getNewValue("$currentValue", "$rangeStrategy", "$currentVersion", "$newVersion") === "$expected"',
     ({ currentValue, rangeStrategy, currentVersion, newVersion, expected }) => {
@@ -248,8 +379,9 @@ describe('modules/versioning/ubuntu/index', () => {
   );
 
   test.each`
-    versions                                        | expected
-    ${['17.03', '18.04', '18.04', '6.10', '19.10']} | ${['6.10', '17.03', '18.04', '18.04', '19.10']}
+    versions                                                  | expected
+    ${['17.03', '18.04', '18.04', '6.10', '19.10']}           | ${['6.10', '17.03', '18.04', '18.04', '19.10']}
+    ${['17.03', 'zesty', 'bionic', 'bionic', 'edgy', 'eoan']} | ${['edgy', '17.03', 'zesty', 'bionic', 'bionic', 'eoan']}
   `('$versions -> sortVersions -> $expected ', ({ versions, expected }) => {
     expect(versions.sort(ubuntu.sortVersions)).toEqual(expected);
   });
diff --git a/lib/modules/versioning/ubuntu/index.ts b/lib/modules/versioning/ubuntu/index.ts
index f1b4833f68a1a5fb79ba4d91998636f0f14b276d..7b57ba9e1c71be7c8c0ea30f61ee4d52d16ca772 100644
--- a/lib/modules/versioning/ubuntu/index.ts
+++ b/lib/modules/versioning/ubuntu/index.ts
@@ -1,5 +1,10 @@
 import { regEx } from '../../../util/regex';
 import type { NewValueConfig, VersioningApi } from '../types';
+import {
+  getCodenameByVersion,
+  getVersionByCodename,
+  isCodename,
+} from './distribution';
 
 export const id = 'ubuntu';
 export const displayName = 'Ubuntu';
@@ -13,8 +18,11 @@ const temporarilyUnstable = ['22.04'];
 
 function isValid(input: string): boolean {
   return (
-    typeof input === 'string' &&
-    regEx(/^(0[4-5]|[6-9]|[1-9][0-9])\.[0-9][0-9](\.[0-9]{1,2})?$/).test(input)
+    (typeof input === 'string' &&
+      regEx(/^(0[4-5]|[6-9]|[1-9][0-9])\.[0-9][0-9](\.[0-9]{1,2})?$/).test(
+        input
+      )) ||
+    isCodename(input)
   );
 }
 
@@ -31,36 +39,40 @@ function isSingleVersion(version: string): boolean {
 }
 
 function isStable(version: string): boolean {
-  if (!isValid(version)) {
+  const ver = getVersionByCodename(version);
+  if (!isValid(ver)) {
     return false;
   }
-  if (temporarilyUnstable.includes(version)) {
+  if (temporarilyUnstable.includes(ver)) {
     return false;
   }
-  return regEx(/^\d?[02468]\.04/).test(version);
+  return regEx(/^\d?[02468]\.04/).test(ver);
 }
 
 // digestion of version
 
 function getMajor(version: string): null | number {
-  if (isValid(version)) {
-    const [major] = version.split('.');
+  const ver = getVersionByCodename(version);
+  if (isValid(ver)) {
+    const [major] = ver.split('.');
     return parseInt(major, 10);
   }
   return null;
 }
 
 function getMinor(version: string): null | number {
-  if (isValid(version)) {
-    const [, minor] = version.split('.');
+  const ver = getVersionByCodename(version);
+  if (isValid(ver)) {
+    const [, minor] = ver.split('.');
     return parseInt(minor, 10);
   }
   return null;
 }
 
 function getPatch(version: string): null | number {
-  if (isValid(version)) {
-    const [, , patch] = version.split('.');
+  const ver = getVersionByCodename(version);
+  if (isValid(ver)) {
+    const [, , patch] = ver.split('.');
     return patch ? parseInt(patch, 10) : null;
   }
   return null;
@@ -69,7 +81,9 @@ function getPatch(version: string): null | number {
 // comparison
 
 function equals(version: string, other: string): boolean {
-  return isVersion(version) && isVersion(other) && version === other;
+  const ver = getVersionByCodename(version);
+  const otherVer = getVersionByCodename(other);
+  return isVersion(ver) && isVersion(otherVer) && ver === otherVer;
 }
 
 function isGreaterThan(version: string, other: string): boolean {
@@ -110,8 +124,16 @@ function minSatisfyingVersion(
   return getSatisfyingVersion(versions, range);
 }
 
-function getNewValue(newValueConfig: NewValueConfig): string {
-  return newValueConfig.newVersion;
+function getNewValue({
+  currentValue,
+  rangeStrategy,
+  currentVersion,
+  newVersion,
+}: NewValueConfig): string {
+  if (isCodename(currentValue)) {
+    return getCodenameByVersion(newVersion);
+  }
+  return getVersionByCodename(newVersion);
 }
 
 function sortVersions(version: string, other: string): number {
diff --git a/package.json b/package.json
index ac7e84490c21ba43e5c49b067e0ebacf4fb6c863..dbb106fb30b744a2eff53b9ce60a93638c898703 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
     "test-schema": "run-s create-json-schema",
     "tsc": "tsc",
     "type-check": "run-s generate:* \"tsc --noEmit {@}\" --",
+    "update:ubuntu-distro-info": "node tools/distro-json-generate.mjs",
     "verify": "node tools/verify.mjs"
   },
   "repository": {
diff --git a/tools/distro-json-generate.mjs b/tools/distro-json-generate.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..5ee96ccbce38f8d9c0464a463c57ae01c3f817a2
--- /dev/null
+++ b/tools/distro-json-generate.mjs
@@ -0,0 +1,73 @@
+import fs from 'fs-extra';
+import shell from 'shelljs';
+
+shell.echo(`Verifying required packages...`);
+
+if (!shell.which(`distro-info`)) {
+  shell.echo('This script requires distro-info, exiting...');
+  shell.exit(2);
+}
+
+if (!shell.which(`sed`)) {
+  shell.echo('This script requires sed, exiting...');
+  shell.exit(2);
+}
+
+shell.echo(`OK`);
+
+const ubuntuDistroInfo = shell.exec(
+  `ubuntu-distro-info --all -f | sed -r 's/Ubuntu|"|LTS |Debian //g; s/([0-9]+.[0-9]+) /\\1 /; s/.*/\\L&/; s/( [a-z]*) [a-z]*/\\1/g; s/^[ \\t]*//'`,
+  { silent: true }
+);
+
+/**
+ * @param {string} str
+ * @returns {{}}
+ */
+function objectify(str) {
+  let obj = {};
+
+  for (const line of str.split(/\r?\n/)) {
+    let [ver, codename] = line.split(' ');
+    // eslint-disable-next-line
+    // @ts-ignore
+    obj[ver] = codename;
+  }
+
+  return obj;
+}
+
+/**
+ * @param {string} file
+ * @param {string} newData
+ */
+async function updateJsonFile(file, newData) {
+  let oldData;
+
+  try {
+    oldData = fs.existsSync(file) ? await fs.readFile(file, 'utf8') : null;
+    // Eliminate formatting
+    oldData = oldData?.replace(/\s/g, '') ?? null;
+  } catch (e) {
+    shell.echo(e.toString());
+    shell.exit(1);
+  }
+
+  const parsedData = JSON.stringify(objectify(newData), undefined, 2);
+
+  if (oldData === parsedData) {
+    shell.echo(`${file} is up to date.`);
+    return;
+  }
+
+  try {
+    shell.echo(`Updating ${file}`);
+    await fs.writeFile(file, parsedData);
+  } catch (e) {
+    shell.echo(e.toString());
+    shell.exit(1);
+  }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-floating-promises
+updateJsonFile(`../data/ubuntu-distro-info.json`, ubuntuDistroInfo.toString());