diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml
index c0e731e5e36498f306b21620cc0d6143ca592435..32bd2931d478ba3c520e48b905f94c65387a017b 100644
--- a/.github/workflows/run_tests.yaml
+++ b/.github/workflows/run_tests.yaml
@@ -22,7 +22,7 @@ jobs:
     - name: Run unit tests
       run: go test -race -covermode atomic -coverprofile=coverage.out ./...
     - name: Convert coverage to lcov
-      uses: jandelgado/gcov2lcov-action@v1.0.9
+      uses: jandelgado/gcov2lcov-action@v1.1.1
     - name: Coveralls
       uses: coverallsapp/github-action@master
       with:
diff --git a/README.md b/README.md
index c34bc6f6f9db065ddc191b7c602deb009375a05f..bf393d2da053db2707669e760acd17652f60e46c 100644
--- a/README.md
+++ b/README.md
@@ -28,13 +28,13 @@ pipelines with no access to Kubernetes API directly, promoting infrastructure as
 
 ### PostgreSQL features
 
-* Supports PostgreSQL 16, starting from 12+
+* Supports PostgreSQL 17, starting from 13+
 * Streaming replication cluster via Patroni
 * Point-In-Time-Recovery with
-[pg_basebackup](https://www.postgresql.org/docs/16/app-pgbasebackup.html) /
+[pg_basebackup](https://www.postgresql.org/docs/17/app-pgbasebackup.html) /
 [WAL-E](https://github.com/wal-e/wal-e) via [Spilo](https://github.com/zalando/spilo)
 * Preload libraries: [bg_mon](https://github.com/CyberDem0n/bg_mon),
-[pg_stat_statements](https://www.postgresql.org/docs/16/pgstatstatements.html),
+[pg_stat_statements](https://www.postgresql.org/docs/17/pgstatstatements.html),
 [pgextwlist](https://github.com/dimitri/pgextwlist),
 [pg_auth_mon](https://github.com/RafiaSabih/pg_auth_mon)
 * Incl. popular Postgres extensions such as
diff --git a/charts/postgres-operator-ui/templates/deployment.yaml b/charts/postgres-operator-ui/templates/deployment.yaml
index 899b07d9730b582d22931ff1453ee685a2f5c22b..fbb9ee08645d836c432235fcd1e5d62c4c84915b 100644
--- a/charts/postgres-operator-ui/templates/deployment.yaml
+++ b/charts/postgres-operator-ui/templates/deployment.yaml
@@ -84,11 +84,11 @@ spec:
                   "limit_iops": 16000,
                   "limit_throughput": 1000,
                   "postgresql_versions": [
+                    "17",
                     "16",
                     "15",
                     "14",
-                    "13",
-                    "12"
+                    "13"
                   ]
                 }
             {{- if .Values.extraEnvs }}
diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml
index 0a1e74613e8591374b2e4c93cf7db0ab063b6f71..058769acff8628dbc060b6b7029ed4599164f42f 100644
--- a/charts/postgres-operator/crds/operatorconfigurations.yaml
+++ b/charts/postgres-operator/crds/operatorconfigurations.yaml
@@ -68,7 +68,7 @@ spec:
                   type: string
               docker_image:
                 type: string
-                default: "ghcr.io/zalando/spilo-16:3.3-p1"
+                default: "ghcr.io/zalando/spilo-17:4.0-p2"
               enable_crd_registration:
                 type: boolean
                 default: true
@@ -167,10 +167,10 @@ spec:
                       type: string
                   minimal_major_version:
                     type: string
-                    default: "12"
+                    default: "13"
                   target_major_version:
                     type: string
-                    default: "16"
+                    default: "17"
               kubernetes:
                 type: object
                 properties:
diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml
index a83f7cc95281316c8aca726babd1efdafdcf177c..8083e5e1dd57ef1d12734977e6b58a397ba15ad7 100644
--- a/charts/postgres-operator/crds/postgresqls.yaml
+++ b/charts/postgres-operator/crds/postgresqls.yaml
@@ -375,11 +375,11 @@ spec:
                   version:
                     type: string
                     enum:
-                      - "12"
                       - "13"
                       - "14"
                       - "15"
                       - "16"
+                      - "17"
                   parameters:
                     type: object
                     additionalProperties:
diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml
index 472be74438c6f2bcd1e90a53939d0bfbd8f85b6a..881ff05d6a6b079a088fb27423d292bbb99d37b0 100644
--- a/charts/postgres-operator/values.yaml
+++ b/charts/postgres-operator/values.yaml
@@ -38,7 +38,7 @@ configGeneral:
   # etcd connection string for Patroni. Empty uses K8s-native DCS.
   etcd_host: ""
   # Spilo docker image
-  docker_image: ghcr.io/zalando/spilo-16:3.3-p1
+  docker_image: ghcr.io/zalando/spilo-17:4.0-p2
 
   # key name for annotation to ignore globally configured instance limits
   # ignore_instance_limits_annotation_key: ""
@@ -89,9 +89,9 @@ configMajorVersionUpgrade:
   # - acid
 
   # minimal Postgres major version that will not automatically be upgraded
-  minimal_major_version: "12"
+  minimal_major_version: "13"
   # target Postgres major version when upgrading clusters automatically
-  target_major_version: "16"
+  target_major_version: "17"
 
 configKubernetes:
   # list of additional capabilities for postgres container
diff --git a/docs/administrator.md b/docs/administrator.md
index 725e93716356ae29421c58386aaa8b69a0ba1fad..b06b4ca85b78c27db0b44d160b79c2f404d4eba5 100644
--- a/docs/administrator.md
+++ b/docs/administrator.md
@@ -1297,7 +1297,7 @@ aws_or_gcp:
 
 If cluster members have to be (re)initialized restoring physical backups
 happens automatically either from the backup location or by running
-[pg_basebackup](https://www.postgresql.org/docs/16/app-pgbasebackup.html)
+[pg_basebackup](https://www.postgresql.org/docs/17/app-pgbasebackup.html)
 on one of the other running instances (preferably replicas if they do not lag
 behind). You can test restoring backups by [cloning](user.md#how-to-clone-an-existing-postgresql-cluster)
 clusters.
diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md
index 610982c734fd5af5c3c73c63a185cd0767846480..8d02ee7d898ca785a6919aa5cc960c632e7d2197 100644
--- a/docs/reference/cluster_manifest.md
+++ b/docs/reference/cluster_manifest.md
@@ -638,7 +638,7 @@ the global configuration before adding the `tls` section'.
 ## Change data capture streams
 
 This sections enables change data capture (CDC) streams via Postgres' 
-[logical decoding](https://www.postgresql.org/docs/16/logicaldecoding.html)
+[logical decoding](https://www.postgresql.org/docs/17/logicaldecoding.html)
 feature and `pgoutput` plugin. While the Postgres operator takes responsibility
 for providing the setup to publish change events, it relies on external tools
 to consume them. At Zalando, we are using a workflow based on
@@ -671,7 +671,7 @@ can have the following properties:
   The CDC operator is following the [outbox pattern](https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/).
   The application is responsible for putting events into a (JSON/B or VARCHAR)
   payload column of the outbox table in the structure of the specified target
-  event type. The operator will create a [PUBLICATION](https://www.postgresql.org/docs/16/logical-replication-publication.html)
+  event type. The operator will create a [PUBLICATION](https://www.postgresql.org/docs/17/logical-replication-publication.html)
   in Postgres for all tables specified for one `database` and `applicationId`.
   The CDC operator will consume from it shortly after transactions are
   committed to the outbox table. The `idColumn` will be used in telemetry for
diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md
index 4d4d16cdbdbb0c0e3173bbc6465c93b511a4c0e6..3bd9e44f7afaee0f45314e7b9edcbf4b9877435a 100644
--- a/docs/reference/operator_parameters.md
+++ b/docs/reference/operator_parameters.md
@@ -94,9 +94,6 @@ Those are top-level keys, containing both leaf keys and groups.
 * **enable_pgversion_env_var**
   With newer versions of Spilo, it is preferable to use `PGVERSION` pod environment variable instead of the setting `postgresql.bin_dir` in the `SPILO_CONFIGURATION` env variable. When this option is true, the operator sets `PGVERSION` and omits `postgresql.bin_dir` from  `SPILO_CONFIGURATION`. When false, the `postgresql.bin_dir` is set. This setting takes precedence over `PGVERSION`; see PR 222 in Spilo. The default is `true`.
 
-* **enable_spilo_wal_path_compat**
-  enables backwards compatible path between Spilo 12 and Spilo 13+ images. The default is `false`.
-
 * **enable_team_id_clustername_prefix**
   To lower the risk of name clashes between clusters of different teams you
   can turn on this flag and the operator will sync only clusters where the
@@ -250,12 +247,12 @@ CRD-configuration, they are grouped under the `major_version_upgrade` key.
 
 * **minimal_major_version**
   The minimal Postgres major version that will not automatically be upgraded
-  when `major_version_upgrade_mode` is set to `"full"`. The default is `"12"`.
+  when `major_version_upgrade_mode` is set to `"full"`. The default is `"13"`.
 
 * **target_major_version**
   The target Postgres major version when upgrading clusters automatically
   which violate the configured allowed `minimal_major_version` when
-  `major_version_upgrade_mode` is set to `"full"`. The default is `"16"`.
+  `major_version_upgrade_mode` is set to `"full"`. The default is `"17"`.
 
 ## Kubernetes resources
 
diff --git a/docs/user.md b/docs/user.md
index 78b30dfe942d69d681f808cc68ab572423624d74..aba65c11dc0a4df16dded98b8c85c2013d001b58 100644
--- a/docs/user.md
+++ b/docs/user.md
@@ -30,7 +30,7 @@ spec:
   databases:
     foo: zalando
   postgresql:
-    version: "16"
+    version: "17"
 ```
 
 Once you cloned the Postgres Operator [repository](https://github.com/zalando/postgres-operator)
@@ -109,7 +109,7 @@ metadata:
 spec:
   [...]
   postgresql:
-    version: "16"
+    version: "17"
     parameters:
       password_encryption: scram-sha-256
 ```
@@ -517,7 +517,7 @@ Postgres Operator will create the following NOLOGIN roles:
 
 The `<dbname>_owner` role is the database owner and should be used when creating
 new database objects. All members of the `admin` role, e.g. teams API roles, can
-become the owner with the `SET ROLE` command. [Default privileges](https://www.postgresql.org/docs/16/sql-alterdefaultprivileges.html)
+become the owner with the `SET ROLE` command. [Default privileges](https://www.postgresql.org/docs/17/sql-alterdefaultprivileges.html)
 are configured for the owner role so that the `<dbname>_reader` role
 automatically gets read-access (SELECT) to new tables and sequences and the
 `<dbname>_writer` receives write-access (INSERT, UPDATE, DELETE on tables,
@@ -594,7 +594,7 @@ spec:
 
 ### Schema `search_path` for default roles
 
-The schema [`search_path`](https://www.postgresql.org/docs/16/ddl-schemas.html#DDL-SCHEMAS-PATH)
+The schema [`search_path`](https://www.postgresql.org/docs/17/ddl-schemas.html#DDL-SCHEMAS-PATH)
 for each role will include the role name and the schemas, this role should have
 access to. So `foo_bar_writer` does not have to schema-qualify tables from
 schemas `foo_bar_writer, bar`, while `foo_writer` can look up `foo_writer` and
@@ -695,7 +695,7 @@ handle it.
 
 ### HugePages support
 
-The operator supports [HugePages](https://www.postgresql.org/docs/16/kernel-resources.html#LINUX-HUGEPAGES).
+The operator supports [HugePages](https://www.postgresql.org/docs/17/kernel-resources.html#LINUX-HUGEPAGES).
 To enable HugePages, set the matching resource requests and/or limits in the manifest:
 
 ```yaml
@@ -838,7 +838,7 @@ spec:
 ### Clone directly
 
 Another way to get a fresh copy of your source DB cluster is via
-[pg_basebackup](https://www.postgresql.org/docs/16/app-pgbasebackup.html). To
+[pg_basebackup](https://www.postgresql.org/docs/17/app-pgbasebackup.html). To
 use this feature simply leave out the timestamp field from the clone section.
 The operator will connect to the service of the source cluster by name. If the
 cluster is called test, then the connection string will look like host=test
diff --git a/e2e/Makefile b/e2e/Makefile
index 8e200dab1d2b4cd3e69ffe67eee4a1d5552dd0ae..52d24e9e5b84746b8d08f789d85c962c3ebdad71 100644
--- a/e2e/Makefile
+++ b/e2e/Makefile
@@ -46,7 +46,7 @@ tools:
 	# install pinned version of 'kind'
 	# go install must run outside of a dir with a (module-based) Go project !
 	# otherwise go install updates project's dependencies and/or behaves differently
-	cd "/tmp" && GO111MODULE=on go install sigs.k8s.io/kind@v0.23.0
+	cd "/tmp" && GO111MODULE=on go install sigs.k8s.io/kind@v0.24.0
 
 e2etest: tools copy clean
 	./run.sh main
diff --git a/e2e/run.sh b/e2e/run.sh
index 1adca479d6d6599f87d61544e2757f67d329f3d4..d289cb3f4145d07dfc0559912dfc8124d39d7791 100755
--- a/e2e/run.sh
+++ b/e2e/run.sh
@@ -8,7 +8,7 @@ IFS=$'\n\t'
 
 readonly cluster_name="postgres-operator-e2e-tests"
 readonly kubeconfig_path="/tmp/kind-config-${cluster_name}"
-readonly spilo_image="registry.opensource.zalan.do/acid/spilo-16-e2e:0.1"
+readonly spilo_image="registry.opensource.zalan.do/acid/spilo-17-e2e:0.3"
 readonly e2e_test_runner_image="registry.opensource.zalan.do/acid/postgres-operator-e2e-tests-runner:0.4"
 
 export GOPATH=${GOPATH-~/go}
diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py
index f5a05a15753ea59a32f87957caea22dde086f13a..04c6465c91ca32ea1c64c44c57ad84fd48663991 100644
--- a/e2e/tests/test_e2e.py
+++ b/e2e/tests/test_e2e.py
@@ -12,10 +12,9 @@ from kubernetes import client
 from tests.k8s_api import K8s
 from kubernetes.client.rest import ApiException
 
-SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-16-e2e:0.1"
-SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-16-e2e:0.2"
-SPILO_FULL_IMAGE = "ghcr.io/zalando/spilo-16:3.2-p3"
-
+SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-17-e2e:0.3"
+SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-17-e2e:0.4"
+SPILO_FULL_IMAGE = "ghcr.io/zalando/spilo-17:4.0-p2"
 
 def to_selector(labels):
     return ",".join(["=".join(lbl) for lbl in labels.items()])
@@ -1201,35 +1200,35 @@ class EndToEndTestCase(unittest.TestCase):
         k8s = self.k8s
         cluster_label = 'application=spilo,cluster-name=acid-upgrade-test'
 
-        with open("manifests/minimal-postgres-manifest-12.yaml", 'r+') as f:
+        with open("manifests/minimal-postgres-lowest-version-manifest.yaml", 'r+') as f:
             upgrade_manifest = yaml.safe_load(f)
             upgrade_manifest["spec"]["dockerImage"] = SPILO_FULL_IMAGE
 
-        with open("manifests/minimal-postgres-manifest-12.yaml", 'w') as f:
+        with open("manifests/minimal-postgres-lowest-version-manifest.yaml", 'w') as f:
             yaml.dump(upgrade_manifest, f, Dumper=yaml.Dumper)
 
-        k8s.create_with_kubectl("manifests/minimal-postgres-manifest-12.yaml")
+        k8s.create_with_kubectl("manifests/minimal-postgres-lowest-version-manifest.yaml")
         self.eventuallyEqual(lambda: k8s.count_running_pods(labels=cluster_label), 2, "No 2 pods running")
         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
-        self.eventuallyEqual(check_version, 12, "Version is not correct")
+        self.eventuallyEqual(check_version, 13, "Version is not correct")
 
         master_nodes, _ = k8s.get_cluster_nodes(cluster_labels=cluster_label)
         # should upgrade immediately
-        pg_patch_version_13 = {
+        pg_patch_version_14 = {
             "spec": {
                 "postgresql": {
-                    "version": "13"
+                    "version": "14"
                 }
             }
         }
         k8s.api.custom_objects_api.patch_namespaced_custom_object(
-            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_13)
+            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_14)
         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
 
         k8s.wait_for_pod_failover(master_nodes, 'spilo-role=replica,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
-        self.eventuallyEqual(check_version, 13, "Version should be upgraded from 12 to 13")
+        self.eventuallyEqual(check_version, 14, "Version should be upgraded from 13 to 14")
 
         # check if annotation for last upgrade's success is set
         annotations = get_annotations()
@@ -1238,10 +1237,10 @@ class EndToEndTestCase(unittest.TestCase):
         # should not upgrade because current time is not in maintenanceWindow
         current_time = datetime.now()
         maintenance_window_future = f"{(current_time+timedelta(minutes=60)).strftime('%H:%M')}-{(current_time+timedelta(minutes=120)).strftime('%H:%M')}"
-        pg_patch_version_14 = {
+        pg_patch_version_15 = {
             "spec": {
                 "postgresql": {
-                    "version": "14"
+                    "version": "15"
                 },
                 "maintenanceWindows": [
                     maintenance_window_future
@@ -1249,23 +1248,23 @@ class EndToEndTestCase(unittest.TestCase):
             }
         }
         k8s.api.custom_objects_api.patch_namespaced_custom_object(
-            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_14)
+            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_15)
         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
 
         k8s.wait_for_pod_failover(master_nodes, 'spilo-role=master,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
-        self.eventuallyEqual(check_version, 13, "Version should not be upgraded")
+        self.eventuallyEqual(check_version, 14, "Version should not be upgraded")
 
         second_annotations = get_annotations()
         self.assertIsNone(second_annotations.get("last-major-upgrade-failure"), "Annotation for last upgrade's failure should not be set")
 
         # change the version again to trigger operator sync
         maintenance_window_current = f"{(current_time-timedelta(minutes=30)).strftime('%H:%M')}-{(current_time+timedelta(minutes=30)).strftime('%H:%M')}"
-        pg_patch_version_15 = {
+        pg_patch_version_16 = {
             "spec": {
                 "postgresql": {
-                    "version": "15"
+                    "version": "16"
                 },
                 "maintenanceWindows": [
                     maintenance_window_current
@@ -1274,13 +1273,13 @@ class EndToEndTestCase(unittest.TestCase):
         }
 
         k8s.api.custom_objects_api.patch_namespaced_custom_object(
-            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_15)
+            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_16)
         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
 
         k8s.wait_for_pod_failover(master_nodes, 'spilo-role=replica,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
-        self.eventuallyEqual(check_version, 15, "Version should be upgraded from 13 to 15")
+        self.eventuallyEqual(check_version, 16, "Version should be upgraded from 14 to 16")
 
         # check if annotation for last upgrade's success is updated after second upgrade
         third_annotations = get_annotations()
@@ -1288,7 +1287,7 @@ class EndToEndTestCase(unittest.TestCase):
         self.assertNotEqual(annotations.get("last-major-upgrade-success"), third_annotations.get("last-major-upgrade-success"), "Annotation for last upgrade's success is not updated")
 
         # test upgrade with failed upgrade annotation
-        pg_patch_version_16 = {
+        pg_patch_version_17 = {
             "metadata": {
                 "annotations": {
                     "last-major-upgrade-failure": "2024-01-02T15:04:05Z"
@@ -1296,18 +1295,18 @@ class EndToEndTestCase(unittest.TestCase):
             },
             "spec": {
                 "postgresql": {
-                    "version": "16"
+                    "version": "17"
                 },
             },
         }
         k8s.api.custom_objects_api.patch_namespaced_custom_object(
-            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_16)
+            "acid.zalan.do", "v1", "default", "postgresqls", "acid-upgrade-test", pg_patch_version_17)
         self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
 
         k8s.wait_for_pod_failover(master_nodes, 'spilo-role=master,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
         k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
-        self.eventuallyEqual(check_version, 15, "Version should not be upgraded because annotation for last upgrade's failure is set")
+        self.eventuallyEqual(check_version, 16, "Version should not be upgraded because annotation for last upgrade's failure is set")
 
         # change the version back to 15 and should remove failure annotation
         k8s.api.custom_objects_api.patch_namespaced_custom_object(
diff --git a/logical-backup/Dockerfile b/logical-backup/Dockerfile
index 8770e5e1ab371a962097eb2fb5776ee8f2cc9a9a..137f4efa8651e2b9ccc4ec8d527cf7999dca2975 100644
--- a/logical-backup/Dockerfile
+++ b/logical-backup/Dockerfile
@@ -25,11 +25,11 @@ RUN apt-get update     \
     && curl --silent https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
     && apt-get update \
     && apt-get install --no-install-recommends -y  \
+        postgresql-client-17  \
         postgresql-client-16  \
         postgresql-client-15  \
         postgresql-client-14  \
         postgresql-client-13  \
-        postgresql-client-12  \
     && apt-get clean \
     && rm -rf /var/lib/apt/lists/*
 
diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml
index 0b3dc4aa793f978196c0d992649f1467b159d932..44d31712379610b7536e7c08e44a3aaf490bde64 100644
--- a/manifests/complete-postgres-manifest.yaml
+++ b/manifests/complete-postgres-manifest.yaml
@@ -10,7 +10,7 @@ metadata:
 #    "delete-date": "2020-08-31"  # can only be deleted on that day if "delete-date "key is configured
 #    "delete-clustername": "acid-test-cluster"  # can only be deleted when name matches if "delete-clustername" key is configured
 spec:
-  dockerImage: ghcr.io/zalando/spilo-16:3.3-p1
+  dockerImage: ghcr.io/zalando/spilo-17:4.0-p2
   teamId: "acid"
   numberOfInstances: 2
   users:  # Application/Robot users
@@ -48,7 +48,7 @@ spec:
           defaultRoles: true
           defaultUsers: false
   postgresql:
-    version: "16"
+    version: "17"
     parameters:  # Expert section
       shared_buffers: "32MB"
       max_connections: "10"
diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml
index 1c8c8fdfd5ce2ec200ff8a2a6b2dc70c7dc54b0f..094bd6bd5e5e76f8ab1b6a9bff46a686a36b5933 100644
--- a/manifests/configmap.yaml
+++ b/manifests/configmap.yaml
@@ -34,7 +34,7 @@ data:
   default_memory_request: 100Mi
   # delete_annotation_date_key: delete-date
   # delete_annotation_name_key: delete-clustername
-  docker_image: ghcr.io/zalando/spilo-16:3.3-p1
+  docker_image: ghcr.io/zalando/spilo-17:4.0-p2
   # downscaler_annotations: "deployment-time,downscaler/*"
   enable_admin_role_for_users: "true"
   enable_crd_registration: "true"
@@ -112,7 +112,7 @@ data:
   min_cpu_limit: 250m
   min_instances: "-1"
   min_memory_limit: 250Mi
-  minimal_major_version: "12"
+  minimal_major_version: "13"
   # node_readiness_label: "status:ready"
   # node_readiness_label_merge: "OR"
   oauth_token_secret_name: postgresql-operator
@@ -162,7 +162,7 @@ data:
   spilo_privileged: "false"
   storage_resize_mode: "pvc"
   super_username: postgres
-  target_major_version: "16"
+  target_major_version: "17"
   team_admin_role: "admin"
   team_api_role_configuration: "log_statement:all"
   teams_api_url: http://fake-teams-api.default.svc.cluster.local
diff --git a/manifests/minimal-postgres-manifest-12.yaml b/manifests/minimal-postgres-lowest-version-manifest.yaml
similarity index 95%
rename from manifests/minimal-postgres-manifest-12.yaml
rename to manifests/minimal-postgres-lowest-version-manifest.yaml
index d578ac46db0d18061a3d212d506ae0b1def94880..40abf0c9c4094e8f83c73a895bbe228d7d427d22 100644
--- a/manifests/minimal-postgres-manifest-12.yaml
+++ b/manifests/minimal-postgres-lowest-version-manifest.yaml
@@ -17,4 +17,4 @@ spec:
   preparedDatabases:
     bar: {}
   postgresql:
-    version: "12"
+    version: "13"
diff --git a/manifests/minimal-postgres-manifest.yaml b/manifests/minimal-postgres-manifest.yaml
index d223279053d3feb5e566098a76eeee381dc15059..8b1ed275dc717b02d3a0ce444e620f25ef68c44d 100644
--- a/manifests/minimal-postgres-manifest.yaml
+++ b/manifests/minimal-postgres-manifest.yaml
@@ -17,4 +17,4 @@ spec:
   preparedDatabases:
     bar: {}
   postgresql:
-    version: "16"
+    version: "17"
diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml
index a7b1a7280457b71bf4efcb88239dd1f2eeffbb52..d4990bf2bc5d8f7b33a9444dbc05c4310701d2c9 100644
--- a/manifests/operatorconfiguration.crd.yaml
+++ b/manifests/operatorconfiguration.crd.yaml
@@ -66,7 +66,7 @@ spec:
                   type: string
               docker_image:
                 type: string
-                default: "ghcr.io/zalando/spilo-16:3.3-p1"
+                default: "ghcr.io/zalando/spilo-17:4.0-p2"
               enable_crd_registration:
                 type: boolean
                 default: true
@@ -165,10 +165,10 @@ spec:
                       type: string
                   minimal_major_version:
                     type: string
-                    default: "12"
+                    default: "13"
                   target_major_version:
                     type: string
-                    default: "16"
+                    default: "17"
               kubernetes:
                 type: object
                 properties:
diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml
index ecb7a03de14653983eb5b2aa1334504c15c35696..db0d13b5f4c1e706d0a1142101f63c9f3411bd6b 100644
--- a/manifests/postgresql-operator-default-configuration.yaml
+++ b/manifests/postgresql-operator-default-configuration.yaml
@@ -3,7 +3,7 @@ kind: OperatorConfiguration
 metadata:
   name: postgresql-operator-default-configuration
 configuration:
-  docker_image: ghcr.io/zalando/spilo-16:3.3-p1
+  docker_image: ghcr.io/zalando/spilo-17:4.0-p2
   # enable_crd_registration: true
   # crd_categories:
   # - all
@@ -39,8 +39,8 @@ configuration:
     major_version_upgrade_mode: "manual"
     # major_version_upgrade_team_allow_list:
     # - acid
-    minimal_major_version: "12"
-    target_major_version: "16"
+    minimal_major_version: "13"
+    target_major_version: "17"
   kubernetes:
     # additional_pod_capabilities:
     # - "SYS_NICE"
diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml
index 9f7e3eff831610929b086f13fa277c18851981d7..39d751cef05546ddf8e776e9f35114fb5533d1c3 100644
--- a/manifests/postgresql.crd.yaml
+++ b/manifests/postgresql.crd.yaml
@@ -373,11 +373,11 @@ spec:
                   version:
                     type: string
                     enum:
-                      - "12"
                       - "13"
                       - "14"
                       - "15"
                       - "16"
+                      - "17"
                   parameters:
                     type: object
                     additionalProperties:
diff --git a/manifests/standby-manifest.yaml b/manifests/standby-manifest.yaml
index aece29dae670aa63f64a657219c597779d7bd4a0..eb90464a6b3069fab00462ea36f961be9c4e25bb 100644
--- a/manifests/standby-manifest.yaml
+++ b/manifests/standby-manifest.yaml
@@ -8,7 +8,7 @@ spec:
     size: 1Gi
   numberOfInstances: 1
   postgresql:
-    version: "16"
+    version: "17"
   # Make this a standby cluster and provide either the s3 bucket path of source cluster or the remote primary host for continuous streaming.
   standby:
     # s3_wal_path: "s3://mybucket/spilo/acid-minimal-cluster/abcd1234-2a4b-4b2a-8c9c-c1234defg567/wal/14/"
diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go
index c5c4b2706ed24ec150a555ad969c77419e008b89..3f6bf25d9e2e1c54dbead78e267c0c4bbda29f06 100644
--- a/pkg/apis/acid.zalan.do/v1/crds.go
+++ b/pkg/apis/acid.zalan.do/v1/crds.go
@@ -595,9 +595,6 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
 							"version": {
 								Type: "string",
 								Enum: []apiextv1.JSON{
-									{
-										Raw: []byte(`"12"`),
-									},
 									{
 										Raw: []byte(`"13"`),
 									},
@@ -610,6 +607,9 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
 									{
 										Raw: []byte(`"16"`),
 									},
+									{
+										Raw: []byte(`"17"`),
+									},
 								},
 							},
 							"parameters": {
@@ -1164,7 +1164,8 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
 						Type: "boolean",
 					},
 					"enable_spilo_wal_path_compat": {
-						Type: "boolean",
+						Type:        "boolean",
+						Description: "deprecated",
 					},
 					"enable_team_id_clustername_prefix": {
 						Type: "boolean",
diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go
index eb01d450c89d038d692af5f3983b2213b005048a..cd11b917316756b8169784bb811ffdcafa001b4d 100644
--- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go
+++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go
@@ -49,8 +49,8 @@ type PostgresUsersConfiguration struct {
 type MajorVersionUpgradeConfiguration struct {
 	MajorVersionUpgradeMode          string   `json:"major_version_upgrade_mode" default:"manual"` // off - no actions, manual - manifest triggers action, full - manifest and minimal version violation trigger upgrade
 	MajorVersionUpgradeTeamAllowList []string `json:"major_version_upgrade_team_allow_list,omitempty"`
-	MinimalMajorVersion              string   `json:"minimal_major_version" default:"12"`
-	TargetMajorVersion               string   `json:"target_major_version" default:"16"`
+	MinimalMajorVersion              string   `json:"minimal_major_version" default:"13"`
+	TargetMajorVersion               string   `json:"target_major_version" default:"17"`
 }
 
 // KubernetesMetaConfiguration defines k8s conf required for all Postgres clusters and the operator itself
diff --git a/pkg/apis/acid.zalan.do/v1/util_test.go b/pkg/apis/acid.zalan.do/v1/util_test.go
index bef6cc3ec93c6dd2e85a5858f0c61d8d257a3253..5e4913ffe571ce7b1e8d7dc052f4a1c3ea2e6884 100644
--- a/pkg/apis/acid.zalan.do/v1/util_test.go
+++ b/pkg/apis/acid.zalan.do/v1/util_test.go
@@ -219,7 +219,7 @@ var unmarshalCluster = []struct {
 	      "127.0.0.1/32"
 	    ],
 	    "postgresql": {
-	      "version": "16",
+	      "version": "17",
 	      "parameters": {
 	        "shared_buffers": "32MB",
 	        "max_connections": "10",
@@ -279,7 +279,7 @@ var unmarshalCluster = []struct {
 			},
 			Spec: PostgresSpec{
 				PostgresqlParam: PostgresqlParam{
-					PgVersion: "16",
+					PgVersion: "17",
 					Parameters: map[string]string{
 						"shared_buffers":  "32MB",
 						"max_connections": "10",
@@ -339,7 +339,7 @@ var unmarshalCluster = []struct {
 			},
 			Error: "",
 		},
-		marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"16","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"pod_priority_class_name":"spilo-pod-priority","volume":{"size":"5Gi","storageClass":"SSD", "subPath": "subdir"},"enableShmVolume":false,"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host    all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"acid","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`),
+		marshal: []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"17","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"pod_priority_class_name":"spilo-pod-priority","volume":{"size":"5Gi","storageClass":"SSD", "subPath": "subdir"},"enableShmVolume":false,"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host    all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}}},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"acid","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"],"clone":{"cluster":"acid-batman"}},"status":{"PostgresClusterStatus":""}}`),
 		err:     nil},
 	{
 		about: "example with clone",
@@ -404,7 +404,7 @@ var postgresqlList = []struct {
 	out   PostgresqlList
 	err   error
 }{
-	{"expect success", []byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"16"},"teamId":"acid","volume":{"size":"10Gi"}},"status":{"PostgresClusterStatus":"Running"}}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
+	{"expect success", []byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"17"},"teamId":"acid","volume":{"size":"10Gi"}},"status":{"PostgresClusterStatus":"Running"}}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`),
 		PostgresqlList{
 			TypeMeta: metav1.TypeMeta{
 				Kind:       "List",
@@ -425,7 +425,7 @@ var postgresqlList = []struct {
 				},
 				Spec: PostgresSpec{
 					ClusterName:         "testcluster42",
-					PostgresqlParam:     PostgresqlParam{PgVersion: "16"},
+					PostgresqlParam:     PostgresqlParam{PgVersion: "17"},
 					Volume:              Volume{Size: "10Gi"},
 					TeamID:              "acid",
 					AllowedSourceRanges: []string{"185.85.220.0/22"},
diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go
index bea229ddad47cf373d98038fc7c506b0059313cd..612e4525a8c02b1320144c31416e326f1ec48713 100644
--- a/pkg/cluster/k8sres_test.go
+++ b/pkg/cluster/k8sres_test.go
@@ -72,18 +72,18 @@ func TestGenerateSpiloJSONConfiguration(t *testing.T) {
 	}{
 		{
 			subtest: "Patroni default configuration",
-			pgParam: &acidv1.PostgresqlParam{PgVersion: "16"},
+			pgParam: &acidv1.PostgresqlParam{PgVersion: "17"},
 			patroni: &acidv1.Patroni{},
 			opConfig: &config.Config{
 				Auth: config.Auth{
 					PamRoleName: "zalandos",
 				},
 			},
-			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/16/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{}}}`,
+			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/17/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{}}}`,
 		},
 		{
 			subtest: "Patroni configured",
-			pgParam: &acidv1.PostgresqlParam{PgVersion: "16"},
+			pgParam: &acidv1.PostgresqlParam{PgVersion: "17"},
 			patroni: &acidv1.Patroni{
 				InitDB: map[string]string{
 					"encoding":       "UTF8",
@@ -102,38 +102,38 @@ func TestGenerateSpiloJSONConfiguration(t *testing.T) {
 				FailsafeMode:          util.True(),
 			},
 			opConfig: &config.Config{},
-			result:   `{"postgresql":{"bin_dir":"/usr/lib/postgresql/16/bin","pg_hba":["hostssl all all 0.0.0.0/0 md5","host    all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"synchronous_mode":true,"synchronous_mode_strict":true,"synchronous_node_count":1,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}},"failsafe_mode":true}}}`,
+			result:   `{"postgresql":{"bin_dir":"/usr/lib/postgresql/17/bin","pg_hba":["hostssl all all 0.0.0.0/0 md5","host    all all 0.0.0.0/0 md5"]},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"},"data-checksums",{"encoding":"UTF8"},{"locale":"en_US.UTF-8"}],"dcs":{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432,"synchronous_mode":true,"synchronous_mode_strict":true,"synchronous_node_count":1,"slots":{"permanent_logical_1":{"database":"foo","plugin":"pgoutput","type":"logical"}},"failsafe_mode":true}}}`,
 		},
 		{
 			subtest: "Patroni failsafe_mode configured globally",
-			pgParam: &acidv1.PostgresqlParam{PgVersion: "16"},
+			pgParam: &acidv1.PostgresqlParam{PgVersion: "17"},
 			patroni: &acidv1.Patroni{},
 			opConfig: &config.Config{
 				EnablePatroniFailsafeMode: util.True(),
 			},
-			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/16/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{"failsafe_mode":true}}}`,
+			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/17/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{"failsafe_mode":true}}}`,
 		},
 		{
 			subtest: "Patroni failsafe_mode configured globally, disabled for cluster",
-			pgParam: &acidv1.PostgresqlParam{PgVersion: "16"},
+			pgParam: &acidv1.PostgresqlParam{PgVersion: "17"},
 			patroni: &acidv1.Patroni{
 				FailsafeMode: util.False(),
 			},
 			opConfig: &config.Config{
 				EnablePatroniFailsafeMode: util.True(),
 			},
-			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/16/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{"failsafe_mode":false}}}`,
+			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/17/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{"failsafe_mode":false}}}`,
 		},
 		{
 			subtest: "Patroni failsafe_mode disabled globally, configured for cluster",
-			pgParam: &acidv1.PostgresqlParam{PgVersion: "16"},
+			pgParam: &acidv1.PostgresqlParam{PgVersion: "17"},
 			patroni: &acidv1.Patroni{
 				FailsafeMode: util.True(),
 			},
 			opConfig: &config.Config{
 				EnablePatroniFailsafeMode: util.False(),
 			},
-			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/16/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{"failsafe_mode":true}}}`,
+			result: `{"postgresql":{"bin_dir":"/usr/lib/postgresql/17/bin"},"bootstrap":{"initdb":[{"auth-host":"md5"},{"auth-local":"trust"}],"dcs":{"failsafe_mode":true}}}`,
 		},
 	}
 	for _, tt := range tests {
@@ -164,15 +164,15 @@ func TestExtractPgVersionFromBinPath(t *testing.T) {
 		},
 		{
 			subTest:  "test current bin path against hard coded template",
-			binPath:  "/usr/lib/postgresql/16/bin",
+			binPath:  "/usr/lib/postgresql/17/bin",
 			template: pgBinariesLocationTemplate,
-			expected: "16",
+			expected: "17",
 		},
 		{
 			subTest:  "test alternative bin path against a matching template",
-			binPath:  "/usr/pgsql-16/bin",
+			binPath:  "/usr/pgsql-17/bin",
 			template: "/usr/pgsql-%v/bin",
-			expected: "16",
+			expected: "17",
 		},
 	}
 
@@ -2148,7 +2148,7 @@ func TestSidecars(t *testing.T) {
 
 	spec = acidv1.PostgresSpec{
 		PostgresqlParam: acidv1.PostgresqlParam{
-			PgVersion: "16",
+			PgVersion: "17",
 			Parameters: map[string]string{
 				"max_connections": "100",
 			},
diff --git a/pkg/cluster/majorversionupgrade.go b/pkg/cluster/majorversionupgrade.go
index 560f8977fc94718daa54cf351bab1a70cb35b680..a4ae5f81bec02fce238ac79237966f1d14148465 100644
--- a/pkg/cluster/majorversionupgrade.go
+++ b/pkg/cluster/majorversionupgrade.go
@@ -21,6 +21,7 @@ var VersionMap = map[string]int{
 	"14": 140000,
 	"15": 150000,
 	"16": 160000,
+	"17": 170000,
 }
 
 const (
@@ -44,7 +45,7 @@ func (c *Cluster) GetDesiredMajorVersionAsInt() int {
 func (c *Cluster) GetDesiredMajorVersion() string {
 
 	if c.Config.OpConfig.MajorVersionUpgradeMode == "full" {
-		// e.g. current is 12, minimal is 12 allowing 12 to 16 clusters, everything below is upgraded
+		// e.g. current is 13, minimal is 13 allowing 13 to 17 clusters, everything below is upgraded
 		if IsBiggerPostgresVersion(c.Spec.PgVersion, c.Config.OpConfig.MinimalMajorVersion) {
 			c.logger.Infof("overwriting configured major version %s to %s", c.Spec.PgVersion, c.Config.OpConfig.TargetMajorVersion)
 			return c.Config.OpConfig.TargetMajorVersion
diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go
index 78e752f1d0d6af334b7ee28b0e35df58a5f15a9e..ba347b2fdd43db4794c750684194049ea26009a0 100644
--- a/pkg/controller/operator_config.go
+++ b/pkg/controller/operator_config.go
@@ -39,7 +39,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 	result.EnableTeamIdClusternamePrefix = fromCRD.EnableTeamIdClusternamePrefix
 	result.EtcdHost = fromCRD.EtcdHost
 	result.KubernetesUseConfigMaps = fromCRD.KubernetesUseConfigMaps
-	result.DockerImage = util.Coalesce(fromCRD.DockerImage, "ghcr.io/zalando/spilo-16:3.3-p1")
+	result.DockerImage = util.Coalesce(fromCRD.DockerImage, "ghcr.io/zalando/spilo-17:4.0-p2")
 	result.Workers = util.CoalesceUInt32(fromCRD.Workers, 8)
 	result.MinInstances = fromCRD.MinInstances
 	result.MaxInstances = fromCRD.MaxInstances
@@ -62,8 +62,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 	// major version upgrade config
 	result.MajorVersionUpgradeMode = util.Coalesce(fromCRD.MajorVersionUpgrade.MajorVersionUpgradeMode, "manual")
 	result.MajorVersionUpgradeTeamAllowList = fromCRD.MajorVersionUpgrade.MajorVersionUpgradeTeamAllowList
-	result.MinimalMajorVersion = util.Coalesce(fromCRD.MajorVersionUpgrade.MinimalMajorVersion, "12")
-	result.TargetMajorVersion = util.Coalesce(fromCRD.MajorVersionUpgrade.TargetMajorVersion, "16")
+	result.MinimalMajorVersion = util.Coalesce(fromCRD.MajorVersionUpgrade.MinimalMajorVersion, "13")
+	result.TargetMajorVersion = util.Coalesce(fromCRD.MajorVersionUpgrade.TargetMajorVersion, "17")
 
 	// kubernetes config
 	result.EnableOwnerReferences = util.CoalesceBool(fromCRD.Kubernetes.EnableOwnerReferences, util.False())
diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go
index 4c7b8db10453a7a39ed836f7ab4a6a10fba946b6..6c76718b7d7588e2183d4126295ddbe14ec2fbb6 100644
--- a/pkg/util/config/config.go
+++ b/pkg/util/config/config.go
@@ -175,7 +175,7 @@ type Config struct {
 	WatchedNamespace        string            `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to'
 	KubernetesUseConfigMaps bool              `name:"kubernetes_use_configmaps" default:"false"`
 	EtcdHost                string            `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS
-	DockerImage             string            `name:"docker_image" default:"ghcr.io/zalando/spilo-16:3.3-p1"`
+	DockerImage             string            `name:"docker_image" default:"ghcr.io/zalando/spilo-17:4.0-p2"`
 	SidecarImages           map[string]string `name:"sidecar_docker_images"` // deprecated in favour of SidecarContainers
 	SidecarContainers       []v1.Container    `name:"sidecars"`
 	PodServiceAccountName   string            `name:"pod_service_account_name" default:"postgres-pod"`
@@ -246,8 +246,8 @@ type Config struct {
 	EnableTeamIdClusternamePrefix            bool              `name:"enable_team_id_clustername_prefix" default:"false"`
 	MajorVersionUpgradeMode                  string            `name:"major_version_upgrade_mode" default:"manual"`
 	MajorVersionUpgradeTeamAllowList         []string          `name:"major_version_upgrade_team_allow_list" default:""`
-	MinimalMajorVersion                      string            `name:"minimal_major_version" default:"12"`
-	TargetMajorVersion                       string            `name:"target_major_version" default:"16"`
+	MinimalMajorVersion                      string            `name:"minimal_major_version" default:"13"`
+	TargetMajorVersion                       string            `name:"target_major_version" default:"17"`
 	PatroniAPICheckInterval                  time.Duration     `name:"patroni_api_check_interval" default:"1s"`
 	PatroniAPICheckTimeout                   time.Duration     `name:"patroni_api_check_timeout" default:"5s"`
 	EnablePatroniFailsafeMode                *bool             `name:"enable_patroni_failsafe_mode" default:"false"`
diff --git a/ui/manifests/deployment.yaml b/ui/manifests/deployment.yaml
index 76d2143cba77fcffdd28983d277c52825be5d1fe..9b003857999a71e08d9efe8aed838e98f336d623 100644
--- a/ui/manifests/deployment.yaml
+++ b/ui/manifests/deployment.yaml
@@ -73,11 +73,11 @@ spec:
                   "limit_iops": 16000,
                   "limit_throughput": 1000,
                   "postgresql_versions": [
+                    "17",
                     "16",
                     "15",
                     "14",
-                    "13",
-                    "12"
+                    "13"
                   ]
                 }
             # Exemple of settings to make snapshot view working in the ui when using AWS
diff --git a/ui/operator_ui/main.py b/ui/operator_ui/main.py
index ba544750f068ea1354b6b4d9e8202d77e3594da0..e02c2995cbccc16a21e53dcf771b2da22af6f824 100644
--- a/ui/operator_ui/main.py
+++ b/ui/operator_ui/main.py
@@ -267,7 +267,7 @@ DEFAULT_UI_CONFIG = {
     'users_visible': True,
     'databases_visible': True,
     'resources_visible': RESOURCES_VISIBLE,
-    'postgresql_versions': ['12', '13', '14', '15', '16'],
+    'postgresql_versions': ['13', '14', '15', '16', '17'],
     'dns_format_string': '{0}.{1}',
     'pgui_link': '',
     'static_network_whitelist': {},
diff --git a/ui/operator_ui/spiloutils.py b/ui/operator_ui/spiloutils.py
index 9de072fca97a48553300417d937c1f3493da91b8..f715430a1da46620e752431a79c8e79f37f141d7 100644
--- a/ui/operator_ui/spiloutils.py
+++ b/ui/operator_ui/spiloutils.py
@@ -305,7 +305,7 @@ def read_versions(
         if uid == 'wal' or defaulting(lambda: UUID(uid))
     ]
 
-BACKUP_VERSION_PREFIXES = ['', '10/', '11/', '12/', '13/', '14/', '15/', '16/']
+BACKUP_VERSION_PREFIXES = ['', '10/', '11/', '12/', '13/', '14/', '15/', '16/', '17/']
 
 def read_basebackups(
     pg_cluster,
diff --git a/ui/run_local.sh b/ui/run_local.sh
index 77f4da7606bb7090ab3ba30799bfe8ffec8af48d..37f8b17479e2106ef813c9ec8a2d50744ee62ae2 100755
--- a/ui/run_local.sh
+++ b/ui/run_local.sh
@@ -31,11 +31,11 @@ default_operator_ui_config='{
   "limit_iops": 16000,
   "limit_throughput": 1000,
   "postgresql_versions": [
+    "17",
     "16",
     "15",
     "14",
-    "13",
-    "12"
+    "13"
   ],
   "static_network_whitelist": {
     "localhost": ["172.0.0.1/32"]