Repository: compose-spec/compose-go
Pull request #62 (2020-07-17 08:08:34)

Merge V2 - V3 compose file formats (optional version field)
This is the compose-go sibling PR for https://github.com/docker/compose/pull/7588 to apply https://github.com/compose-spec/compose-spec/issues/13

Note: this PR **don't** add support for compose file v1, something we could consider later


Patch:
diff --git a/go.mod b/go.mod
index 6827819..0f31293 100644
--- a/go.mod
+++ b/go.mod
@@ -10,9 +10,11 @@ require (
 	github.com/joho/godotenv v1.3.0
 	github.com/mattn/go-shellwords v1.0.10
 	github.com/mitchellh/mapstructure v1.3.2
+	github.com/mjibson/esc v0.2.0 // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.6.0
 	github.com/xeipuuv/gojsonschema v1.2.0
+	golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6 // indirect
 	gopkg.in/yaml.v2 v2.3.0
 	gotest.tools/v3 v3.0.2
 )
diff --git a/go.sum b/go.sum
index f72240e..e6c08c4 100644
--- a/go.sum
+++ b/go.sum
@@ -26,6 +26,8 @@ github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5
 github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
 github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mjibson/esc v0.2.0 h1:k96hdaR9Z+nMcnDwNrOvhdBqtjyMrbVyxLpsRCdP2mA=
+github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -44,14 +46,32 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 h1:1mMox4TgefDwqluYCv677yNXwlfTkija4owZve/jr78=
 golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6 h1:nULzSsKgihxFGLnQFv2T7lE5vIhOtg8ZPpJHapEt7o0=
+golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
diff --git a/loader/full-example.yml b/loader/full-example.yml
index a2efd9d..cb5fcd9 100644
--- a/loader/full-example.yml
+++ b/loader/full-example.yml
@@ -1,5 +1,3 @@
-version: "3.9"
-
 services:
   foo:
 
diff --git a/loader/loader.go b/loader/loader.go
index cabab79..e1cab0b 100644
--- a/loader/loader.go
+++ b/loader/loader.go
@@ -18,6 +18,7 @@ package loader
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -26,6 +27,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/imdario/mergo"
+
 	"github.com/compose-spec/compose-go/envfile"
 	interp "github.com/compose-spec/compose-go/interpolation"
 	"github.com/compose-spec/compose-go/schema"
@@ -102,13 +105,6 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
 	configs := []*types.Config{}
 	for _, file := range configDetails.ConfigFiles {
 		configDict := file.Config
-		version := schema.Version(configDict)
-		if configDetails.Version == "" {
-			configDetails.Version = version
-		}
-		if configDetails.Version != version {
-			return nil, errors.Errorf("version mismatched between two composefiles : %v and %v", configDetails.Version, version)
-		}
 
 		if !opts.SkipInterpolation {
 			var err error
@@ -119,7 +115,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
 		}
 
 		if !opts.SkipValidation {
-			if err := schema.Validate(configDict, configDetails.Version); err != nil {
+			if err := schema.Validate(configDict); err != nil {
 				return nil, err
 			}
 		}
@@ -192,9 +188,7 @@ func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interfac
 
 func loadSections(config map[string]interface{}, configDetails types.ConfigDetails) (*types.Config, error) {
 	var err error
-	cfg := types.Config{
-		Version: schema.Version(config),
-	}
+	cfg := types.Config{}
 
 	var loaders = []struct {
 		key string
@@ -428,6 +422,39 @@ func LoadServices(servicesDict map[string]interface{}, workingDir string, lookup
 		if err != nil {
 			return nil, err
 		}
+
+		if serviceConfig.Extends != nil {
+			file := serviceConfig.Extends["file"]
+			service := serviceConfig.Extends["service"]
+			var source interface{}
+			if file == nil {
+				// extends a service from same file
+				source = servicesDict[*service]
+			} else {
+				if !filepath.IsAbs(*file) {
+					absolute := filepath.Join(workingDir, *file)
+					file = &absolute
+				}
+				bytes, err := ioutil.ReadFile(*file)
+				if err != nil {
+					return nil, err
+				}
+				source, err = ParseYAML(bytes)
+				if err != nil {
+					return nil, err
+				}
+			}
+			baseService, err := LoadService(name, source.(map[string]interface{}), workingDir, lookupEnv)
+			if err != nil {
+				return nil, err
+			}
+
+			if err := mergo.Merge(baseService, serviceConfig, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil {
+				return nil, errors.Wrapf(err, "cannot merge service %s", name)
+			}
+			serviceConfig = baseService
+		}
+
 		services = append(services, *serviceConfig)
 	}
 
diff --git a/loader/merge.go b/loader/merge.go
index e41d672..521dac3 100644
--- a/loader/merge.go
+++ b/loader/merge.go
@@ -29,6 +29,18 @@ type specials struct {
 	m map[reflect.Type]func(dst, src reflect.Value) error
 }
 
+var serviceSpecials = &specials{
+	m: map[reflect.Type]func(dst, src reflect.Value) error{
+		reflect.TypeOf(&types.LoggingConfig{}):           safelyMerge(mergeLoggingConfig),
+		reflect.TypeOf(&types.UlimitsConfig{}):           safelyMerge(mergeUlimitsConfig),
+		reflect.TypeOf([]types.ServicePortConfig{}):      mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
+		reflect.TypeOf([]types.ServiceSecretConfig{}):    mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
+		reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
+		reflect.TypeOf(&types.UlimitsConfig{}):           mergeUlimitsConfig,
+		reflect.TypeOf(&types.ServiceNetworkConfig{}):    mergeServiceNetworkConfig,
+	},
+}
+
 func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error {
 	if fn, ok := s.m[t]; ok {
 		return fn
@@ -67,21 +79,10 @@ func merge(configs []*types.Config) (*types.Config, error) {
 func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {
 	baseServices := mapByName(base)
 	overrideServices := mapByName(override)
-	specials := &specials{
-		m: map[reflect.Type]func(dst, src reflect.Value) error{
-			reflect.TypeOf(&types.LoggingConfig{}):           safelyMerge(mergeLoggingConfig),
-			reflect.TypeOf(&types.UlimitsConfig{}):           safelyMerge(mergeUlimitsConfig),
-			reflect.TypeOf([]types.ServicePortConfig{}):      mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
-			reflect.TypeOf([]types.ServiceSecretConfig{}):    mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
-			reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
-			reflect.TypeOf(&types.UlimitsConfig{}):           mergeUlimitsConfig,
-			reflect.TypeOf(&types.ServiceNetworkConfig{}):    mergeServiceNetworkConfig,
-		},
-	}
 	for name, overrideService := range overrideServices {
 		overrideService := overrideService
 		if baseService, ok := baseServices[name]; ok {
-			if err := mergo.Merge(&baseService, &overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(specials)); err != nil {
+			if err := mergo.Merge(&baseService, &overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil {
 				return base, errors.Wrapf(err, "cannot merge service %s", name)
 			}
 			baseServices[name] = baseService
diff --git a/schema/bindata.go b/schema/bindata.go
index a5be644..b70dc35 100644
--- a/schema/bindata.go
+++ b/schema/bindata.go
@@ -1,4 +1,4 @@
-// Code generated by "esc -o bindata.go -pkg schema -ignore .*.go -private data"; DO NOT EDIT.
+// Code generated by "esc -o bindata.go -pkg schema -ignore .*\.go -private -modtime=1518458244 data"; DO NOT EDIT.
 
 package schema
 
@@ -209,48 +209,60 @@ func _escFSMustString(useLocal bool, name string) string {
 
 var _escData = map[string]*_escFile{
 
-	"/data/config_schema_v3.9.json": {
-		name:    "config_schema_v3.9.json",
-		local:   "data/config_schema_v3.9.json",
-		size:    18246,
-		modtime: 1576078020,
+	"/data/compose-spec.json": {
+		name:    "compose-spec.json",
+		local:   "data/compose-spec.json",
+		size:    24935,
+		modtime: 1518458244,
 		compressed: `
-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
-vNT9GICnn3Kc4a+q/59W/w0AAP//CCwovkZHAAA=
+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
+AAD//6R8c/NnYQAA
 `,
 	},
 
@@ -264,6 +276,6 @@ vNT9GICnn3Kc4a+q/59W/w0AAP//CCwovkZHAAA=
 var _escDirs = map[string][]os.FileInfo{
 
 	"data": {
-		_escData["/data/config_schema_v3.9.json"],
+		_escData["/data/compose-spec.json"],
 	},
 }
diff --git a/schema/data/config_schema_v3.9.json b/schema/data/compose-spec.json
similarity index 65%
rename from schema/data/config_schema_v3.9.json
rename to schema/data/compose-spec.json
index f6b3e9c..1d025cf 100644
--- a/schema/data/config_schema_v3.9.json
+++ b/schema/data/compose-spec.json
@@ -1,12 +1,14 @@
 {
-  "$schema": "http://json-schema.org/draft-04/schema#",
-  "id": "config_schema_v3.9.json",
+  "$schema": "http://json-schema.org/draft/2019-09/schema#",
+  "id": "compose_spec.json",
   "type": "object",
-  "required": ["version"],
+  "title": "Compose Specification",
+  "description": "The Compose file is a YAML file defining a multi-containers based application.",
 
   "properties": {
     "version": {
-      "type": "string"
+      "type": "string",
+      "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
     },
 
     "services": {
@@ -88,12 +90,42 @@
                 "cache_from": {"$ref": "#/definitions/list_of_strings"},
                 "network": {"type": "string"},
                 "target": {"type": "string"},
-                "shm_size": {"type": ["integer", "string"]}
+                "shm_size": {"type": ["integer", "string"]},
+                "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
+                "isolation": {"type": "string"}
               },
-              "additionalProperties": false
+              "additionalProperties": false,
+              "patternProperties": {"^x-": {}}
             }
           ]
         },
+        "blkio_config": {
+          "type": "object",
+          "properties": {
+            "device_read_bps": {
+              "type": "array",
+              "items": {"$ref": "#/definitions/blkio_limit"}
+            },
+            "device_read_iops": {
+              "type": "array",
+              "items": {"$ref": "#/definitions/blkio_limit"}
+            },
+            "device_write_bps": {
+              "type": "array",
+              "items": {"$ref": "#/definitions/blkio_limit"}
+            },
+            "device_write_iops": {
+              "type": "array",
+              "items": {"$ref": "#/definitions/blkio_limit"}
+            },
+            "weight": {"type": "integer"},
+            "weight_device": {
+              "type": "array",
+              "items": {"$ref": "#/definitions/blkio_weight"}
+            }
+          },
+          "additionalProperties": false
+        },
         "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
         "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
         "cgroup_parent": {"type": "string"},
@@ -116,12 +148,23 @@
                   "uid": {"type": "string"},
                   "gid": {"type": "string"},
                   "mode": {"type": "number"}
-                }
+                },
+                "additionalProperties": false,
+                "patternProperties": {"^x-": {}}
               }
             ]
           }
         },
         "container_name": {"type": "string"},
+        "cpu_count": {"type": "integer", "minimum": 0},
+        "cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
+        "cpu_shares": {"type": ["number", "string"]},
+        "cpu_quota": {"type": ["number", "string"]},
+        "cpu_period": {"type": ["number", "string"]},
+        "cpu_rt_period": {"type": ["number", "string"]},
+        "cpu_rt_runtime": {"type": ["number", "string"]},
+        "cpus": {"type": "number", "minimum": 0},
+        "cpuset": {"type": "string"},
         "credential_spec": {
           "type": "object",
           "properties": {
@@ -129,11 +172,35 @@
             "file": {"type": "string"},
             "registry": {"type": "string"}
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
+        },
+        "depends_on": {
+          "oneOf": [
+            {"$ref": "#/definitions/list_of_strings"},
+            {
+              "type": "object",
+              "additionalProperties": false,
+              "patternProperties": {
+                "^[a-zA-Z0-9._-]+$": {
+                  "type": "object",
+                  "additionalProperties": false,
+                  "properties": {
+                    "condition": {
+                      "type": "string",
+                      "enum": ["service_started", "service_healthy"]
+                    }
+                  },
+                  "required": ["condition"]
+                }
+              }
+            }
+          ]
         },
-        "depends_on": {"$ref": "#/definitions/list_of_strings"},
+        "device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
         "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
         "dns": {"$ref": "#/definitions/string_or_list"},
+        "dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true},
         "dns_search": {"$ref": "#/definitions/string_or_list"},
         "domainname": {"type": "string"},
         "entrypoint": {
@@ -153,9 +220,30 @@
           },
           "uniqueItems": true
         },
+        "extends": {
+          "oneOf": [
+            {"type": "string"},
+            {
+              "type": "object",
 
+              "properties": {
+                "service": {"type": "string"},
+                "file": {"type": "string"}
+              },
+              "required": ["service"],
+              "additionalProperties": false
+            }
+          ]
+        },
         "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
         "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
+        "group_add": {
+          "type": "array",
+          "items": {
+            "type": ["string", "number"]
+          },
+          "uniqueItems": true
+        },
         "healthcheck": {"$ref": "#/definitions/healthcheck"},
         "hostname": {"type": "string"},
         "image": {"type": "string"},
@@ -164,25 +252,27 @@
         "isolation": {"type": "string"},
         "labels": {"$ref": "#/definitions/list_or_dict"},
         "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
-
         "logging": {
-            "type": "object",
+          "type": "object",
 
-            "properties": {
-                "driver": {"type": "string"},
-                "options": {
-                  "type": "object",
-                  "patternProperties": {
-                    "^.+$": {"type": ["string", "number", "null"]}
-                  }
-                }
-            },
-            "additionalProperties": false
+          "properties": {
+            "driver": {"type": "string"},
+            "options": {
+              "type": "object",
+              "patternProperties": {
+                "^.+$": {"type": ["string", "number", "null"]}
+              }
+            }
+          },
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
-
         "mac_address": {"type": "string"},
+        "mem_limit": {"type": ["number", "string"]},
+        "mem_reservation": {"type": ["string", "integer"]},
+        "mem_swappiness": {"type": "integer"},
+        "memswap_limit": {"type": ["number", "string"]},
         "network_mode": {"type": "string"},
-
         "networks": {
           "oneOf": [
             {"$ref": "#/definitions/list_of_strings"},
@@ -196,9 +286,12 @@
                       "properties": {
                         "aliases": {"$ref": "#/definitions/list_of_strings"},
                         "ipv4_address": {"type": "string"},
-                        "ipv6_address": {"type": "string"}
+                        "ipv6_address": {"type": "string"},
+                        "link_local_ips": {"$ref": "#/definitions/list_of_strings"},
+                        "priority": {"type": "number"}
                       },
-                      "additionalProperties": false
+                      "additionalProperties": false,
+                      "patternProperties": {"^x-": {}}
                     },
                     {"type": "null"}
                   ]
@@ -208,8 +301,11 @@
             }
           ]
         },
+        "oom_kill_disable": {"type": "boolean"},
+        "oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
         "pid": {"type": ["string", "null"]},
-
+        "pids_limit": {"type": ["number", "string"]},
+        "platform": {"type": "string"},
         "ports": {
           "type": "array",
           "items": {
@@ -224,16 +320,26 @@
                   "published": {"type": "integer"},
                   "protocol": {"type": "string"}
                 },
-                "additionalProperties": false
+                "additionalProperties": false,
+                "patternProperties": {"^x-": {}}
               }
             ]
           },
           "uniqueItems": true
         },
-
         "privileged": {"type": "boolean"},
+        "pull_policy": {"type": "string", "enum": [
+          "always", "never", "if_not_present"
+        ]},
         "read_only": {"type": "boolean"},
         "restart": {"type": "string"},
+        "runtime": {
+          "deprecated": true,
+          "type": "string"
+        },
+        "scale": {
+          "type": "integer"
+        },
         "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
         "shm_size": {"type": ["number", "string"]},
         "secrets": {
@@ -249,7 +355,9 @@
                   "uid": {"type": "string"},
                   "gid": {"type": "string"},
                   "mode": {"type": "number"}
-                }
+                },
+                "additionalProperties": false,
+                "patternProperties": {"^x-": {}}
               }
             ]
           }
@@ -267,13 +375,14 @@
               "oneOf": [
                 {"type": "integer"},
                 {
-                  "type":"object",
+                  "type": "object",
                   "properties": {
                     "hard": {"type": "integer"},
                     "soft": {"type": "integer"}
                   },
                   "required": ["soft", "hard"],
-                  "additionalProperties": false
+                  "additionalProperties": false,
+                  "patternProperties": {"^x-": {}}
                 }
               ]
             }
@@ -299,13 +408,17 @@
                     "type": "object",
                     "properties": {
                       "propagation": {"type": "string"}
-                    }
+                    },
+                    "additionalProperties": false,
+                    "patternProperties": {"^x-": {}}
                   },
                   "volume": {
                     "type": "object",
                     "properties": {
                       "nocopy": {"type": "boolean"}
-                    }
+                    },
+                    "additionalProperties": false,
+                    "patternProperties": {"^x-": {}}
                   },
                   "tmpfs": {
                     "type": "object",
@@ -314,15 +427,23 @@
                         "type": "integer",
                         "minimum": 0
                       }
-                    }
+                    },
+                    "additionalProperties": false,
+                    "patternProperties": {"^x-": {}}
                   }
                 },
-                "additionalProperties": false
+                "additionalProperties": false,
+                "patternProperties": {"^x-": {}}
               }
             ],
             "uniqueItems": true
           }
         },
+        "volumes_from": {
+          "type": "array",
+          "items": {"type": "string"},
+          "uniqueItems": true
+        },
         "working_dir": {"type": "string"}
       },
       "patternProperties": {"^x-": {}},
@@ -332,7 +453,6 @@
     "healthcheck": {
       "id": "#/definitions/healthcheck",
       "type": "object",
-      "additionalProperties": false,
       "properties": {
         "disable": {"type": "boolean"},
         "interval": {"type": "string", "format": "duration"},
@@ -345,7 +465,9 @@
         },
         "timeout": {"type": "string", "format": "duration"},
         "start_period": {"type": "string", "format": "duration"}
-      }
+      },
+      "additionalProperties": false,
+      "patternProperties": {"^x-": {}}
     },
     "deployment": {
       "id": "#/definitions/deployment",
@@ -367,7 +489,8 @@
               "start-first", "stop-first"
             ]}
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "update_config": {
           "type": "object",
@@ -381,7 +504,8 @@
               "start-first", "stop-first"
             ]}
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "resources": {
           "type": "object",
@@ -392,7 +516,8 @@
                 "cpus": {"type": "string"},
                 "memory": {"type": "string"}
               },
-              "additionalProperties": false
+              "additionalProperties": false,
+              "patternProperties": {"^x-": {}}
             },
             "reservations": {
               "type": "object",
@@ -401,10 +526,12 @@
                 "memory": {"type": "string"},
                 "generic_resources": {"$ref": "#/definitions/generic_resources"}
               },
-              "additionalProperties": false
+              "additionalProperties": false,
+              "patternProperties": {"^x-": {}}
             }
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "restart_policy": {
           "type": "object",
@@ -414,7 +541,8 @@
             "max_attempts": {"type": "integer"},
             "window": {"type": "string", "format": "duration"}
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "placement": {
           "type": "object",
@@ -427,15 +555,18 @@
                 "properties": {
                   "spread": {"type": "string"}
                 },
-                "additionalProperties": false
+                "additionalProperties": false,
+                "patternProperties": {"^x-": {}}
               }
             },
             "max_replicas_per_node": {"type": "integer"}
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         }
       },
-      "additionalProperties": false
+      "additionalProperties": false,
+      "patternProperties": {"^x-": {}}
     },
 
     "generic_resources": {
@@ -450,10 +581,12 @@
               "kind": {"type": "string"},
               "value": {"type": "number"}
             },
-            "additionalProperties": false
+            "additionalProperties": false,
+            "patternProperties": {"^x-": {}}
           }
         },
-        "additionalProperties": false
+        "additionalProperties": false,
+        "patternProperties": {"^x-": {}}
       }
     },
 
@@ -478,27 +611,45 @@
               "items": {
                 "type": "object",
                 "properties": {
-                  "subnet": {"type": "string"}
-                },
-                "additionalProperties": false
-              }
+                  "subnet": {"type": "string", "format": "subnet_ip_address"},
+                  "ip_range": {"type": "string"},
+                  "gateway": {"type": "string"},
+                  "aux_addresses": {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "patternProperties": {"^.+$": {"type": "string"}}
+                  }
+                }
+              },
+              "additionalProperties": false,
+              "patternProperties": {"^x-": {}}
+            },
+            "options": {
+              "type": "object",
+              "additionalProperties": false,
+              "patternProperties": {"^.+$": {"type": "string"}}
             }
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "external": {
           "type": ["boolean", "object"],
           "properties": {
-            "name": {"type": "string"}
+            "name": {
+              "deprecated": true,
+              "type": "string"
+            }
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "internal": {"type": "boolean"},
         "attachable": {"type": "boolean"},
         "labels": {"$ref": "#/definitions/list_or_dict"}
       },
-      "patternProperties": {"^x-": {}},
-      "additionalProperties": false
+      "additionalProperties": false,
+      "patternProperties": {"^x-": {}}
     },
 
     "volume": {
@@ -516,14 +667,18 @@
         "external": {
           "type": ["boolean", "object"],
           "properties": {
-            "name": {"type": "string"}
+            "name": {
+              "deprecated": true,
+              "type": "string"
+            }
           },
-          "additionalProperties": false
+          "additionalProperties": false,
+          "patternProperties": {"^x-": {}}
         },
         "labels": {"$ref": "#/definitions/list_or_dict"}
       },
-      "patternProperties": {"^x-": {}},
-      "additionalProperties": false
+      "additionalProperties": false,
+      "patternProperties": {"^x-": {}}
     },
 
     "secret": {
@@ -548,8 +703,8 @@
         },
         "template_driver": {"type": "string"}
       },
-      "patternProperties": {"^x-": {}},
-      "additionalProperties": false
+      "additionalProperties": false,
+      "patternProperties": {"^x-": {}}
     },
 
     "config": {
@@ -561,14 +716,17 @@
         "external": {
           "type": ["boolean", "object"],
           "properties": {
-            "name": {"type": "string"}
+            "name": {
+              "deprecated": true,
+              "type": "string"
+            }
           }
         },
         "labels": {"$ref": "#/definitions/list_or_dict"},
         "template_driver": {"type": "string"}
       },
-      "patternProperties": {"^x-": {}},
-      "additionalProperties": false
+      "additionalProperties": false,
+      "patternProperties": {"^x-": {}}
     },
 
     "string_or_list": {
@@ -599,6 +757,23 @@
       ]
     },
 
+    "blkio_limit": {
+      "type": "object",
+      "properties": {
+        "path": {"type": "string"},
+        "rate": {"type": ["integer", "string"]}
+      },
+      "additionalProperties": false
+    },
+    "blkio_weight": {
+      "type": "object",
+      "properties": {
+        "path": {"type": "string"},
+        "weight": {"type": "integer"}
+      },
+      "additionalProperties": false
+    },
+
     "constraints": {
       "service": {
         "id": "#/definitions/constraints/service",
diff --git a/schema/schema.go b/schema/schema.go
index dd2093c..b876b96 100644
--- a/schema/schema.go
+++ b/schema/schema.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/pkg/errors"
 	"github.com/xeipuuv/gojsonschema"
 )
 
@@ -56,32 +55,11 @@ func init() {
 	gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
 }
 
-// Version returns the version of the config, defaulting to version 1.0
-func Version(config map[string]interface{}) string {
-	version, ok := config[versionField]
-	if !ok {
-		return defaultVersion
-	}
-	return normalizeVersion(fmt.Sprintf("%v", version))
-}
-
-func normalizeVersion(version string) string {
-	switch version {
-	case "3":
-		return "3.9" // latest
-	case "3.0", "3.1", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8":
-		return "3.9" // pre-existing specification but backward compatible
-	default:
-		return version
-	}
-}
-
 // Validate uses the jsonschema to validate the configuration
-func Validate(config map[string]interface{}, version string) error {
-	version = normalizeVersion(version)
-	schemaData, err := _escFSByte(false, fmt.Sprintf("/data/config_schema_v%s.json", version))
+func Validate(config map[string]interface{}) error {
+	schemaData, err := _escFSByte(false, "/data/compose-spec.json")
 	if err != nil {
-		return errors.Errorf("unsupported Compose file version: %s", version)
+		return err
 	}
 
 	schemaLoader := gojsonschema.NewStringLoader(string(schemaData))
diff --git a/types/config.go b/types/config.go
index 4b1394f..7e2acf3 100644
--- a/types/config.go
+++ b/types/config.go
@@ -43,7 +43,6 @@ type ConfigFile struct {
 // Config is a full compose file configuration and model
 type Config struct {
 	Filename   string                 `yaml:"-" json:"-"`
-	Version    string                 `json:"version"`
 	Services   Services               `json:"services"`
 	Networks   Networks               `yaml:",omitempty" json:"networks,omitempty"`
 	Volumes    Volumes                `yaml:",omitempty" json:"volumes,omitempty"`
@@ -67,7 +66,6 @@ type Configs map[string]ConfigObjConfig
 // MarshalJSON makes Config implement json.Marshaler
 func (c Config) MarshalJSON() ([]byte, error) {
 	m := map[string]interface{}{
-		"version":  c.Version,
 		"services": c.Services,
 	}
 


Test patch:
diff --git a/cli/testdata/simple/compose-with-overrides.yaml b/cli/testdata/simple/compose-with-overrides.yaml
index 3dc8a0b..e6d2ba1 100644
--- a/cli/testdata/simple/compose-with-overrides.yaml
+++ b/cli/testdata/simple/compose-with-overrides.yaml
@@ -1,4 +1,3 @@
-version: "3"
 services:
   simple:
     image: haproxy
diff --git a/cli/testdata/simple/compose-with-variables.yaml b/cli/testdata/simple/compose-with-variables.yaml
index 57c29d7..5f91831 100644
--- a/cli/testdata/simple/compose-with-variables.yaml
+++ b/cli/testdata/simple/compose-with-variables.yaml
@@ -1,4 +1,3 @@
-version: "3"
 services:
   simple:
     image: nginx
diff --git a/cli/testdata/simple/compose.yaml b/cli/testdata/simple/compose.yaml
index 431d516..6b8470f 100644
--- a/cli/testdata/simple/compose.yaml
+++ b/cli/testdata/simple/compose.yaml
@@ -1,4 +1,3 @@
-version: "3"
 services:
   simple:
     image: nginx
diff --git a/compatibility/allowlist_test.go b/compatibility/allowlist_test.go
index c991a4e..b1cb992 100644
--- a/compatibility/allowlist_test.go
+++ b/compatibility/allowlist_test.go
@@ -37,7 +37,6 @@ func TestAllowList(t *testing.T) {
 		},
 	}
 	dict, err := loader.ParseYAML([]byte(`
-version: "3"
 services:
   foo:
     image: busybox
diff --git a/loader/full-struct_test.go b/loader/full-struct_test.go
index 0aa84b6..5ef1244 100644
--- a/loader/full-struct_test.go
+++ b/loader/full-struct_test.go
@@ -27,7 +27,6 @@ import (
 
 func fullExampleConfig(workingDir, homeDir string) *types.Config {
 	return &types.Config{
-		Version:  "3.9",
 		Services: services(workingDir, homeDir),
 		Networks: networks(),
 		Volumes:  volumes(),
@@ -541,8 +540,7 @@ func secrets(workingDir string) map[string]types.SecretConfig {
 }
 
 func fullExampleYAML(workingDir, homeDir string) string {
-	return fmt.Sprintf(`version: "3.9"
-services:
+	return fmt.Sprintf(`services:
   foo:
     build:
       context: ./dir
@@ -1427,7 +1425,6 @@ func fullExampleJSON(workingDir, homeDir string) string {
       "working_dir": "/code"
     }
   },
-  "version": "3.9",
   "volumes": {
     "another-volume": {
       "name": "user_specified_name",
diff --git a/loader/loader_test.go b/loader/loader_test.go
index 4e08017..461f818 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -64,7 +64,6 @@ func loadYAMLWithEnv(yaml string, env map[string]string) (*types.Project, error)
 }
 
 var sampleYAML = `
-version: "3"
 services:
   foo:
     image: busybox
@@ -94,7 +93,6 @@ networks:
 `
 
 var sampleDict = map[string]interface{}{
-	"version": "3",
 	"services": map[string]interface{}{
 		"foo": map[string]interface{}{
 			"image":    "busybox",
@@ -200,7 +198,6 @@ func strPtr(val string) *string {
 }
 
 var sampleConfig = types.Config{
-	Version: "3.9",
 	Services: []types.ServiceConfig{
 		{
 			Name:        "foo",
@@ -266,7 +263,6 @@ func TestLoad(t *testing.T) {
 
 func TestLoadExtensions(t *testing.T) {
 	actual, err := loadYAML(`
-version: "3"
 services:
   foo:
     image: busybox
@@ -281,9 +277,26 @@ services:
 	assert.Check(t, is.DeepEqual(extras, service.Extensions))
 }
 
-func TestLoadv39(t *testing.T) {
+func TestLoadExtends(t *testing.T) {
+	actual, err := loadYAML(`
+services:
+  foo:
+    image: busybox
+    extends: 
+      service: bar
+  bar:
+    image: alpine
+    command: echo`)
+	assert.NilError(t, err)
+	assert.Check(t, is.Len(actual.Services, 2))
+	service, err := actual.GetService("foo")
+	assert.NilError(t, err)
+	assert.Check(t, service.Image == "busybox")
+	assert.Check(t, service.Command[0] == "echo")
+}
+
+func TestLoadCredentialSpec(t *testing.T) {
 	actual, err := loadYAML(`
-version: "3.9"
 services:
   foo:
     image: busybox
@@ -316,7 +329,6 @@ func TestInvalidTopLevelObjectType(t *testing.T) {
 
 func TestNonStringKeys(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 123:
   foo:
     image: busybox
@@ -324,7 +336,6 @@ version: "3"
 	assert.ErrorContains(t, err, "Non-string key at top level: 123")
 
 	_, err = loadYAML(`
-version: "3"
 services:
   foo:
     image: busybox
@@ -334,7 +345,6 @@ services:
 	assert.ErrorContains(t, err, "Non-string key in services: 123")
 
 	_, err = loadYAML(`
-version: "3"
 services:
   foo:
     image: busybox
@@ -347,7 +357,6 @@ networks:
 	assert.ErrorContains(t, err, "Non-string key in networks.default.ipam.config[0]: 123")
 
 	_, err = loadYAML(`
-version: "3"
 services:
   dict-env:
     image: busybox
@@ -357,63 +366,16 @@ services:
 	assert.ErrorContains(t, err, "Non-string key in services.dict-env.environment: 1")
 }
 
-func TestSupportedVersion(t *testing.T) {
-	_, err := loadYAML(`
-version: "3"
-services:
-  foo:
-    image: busybox
-`)
-	assert.NilError(t, err)
-
-	_, err = loadYAML(`
-version: "3.9"
-services:
-  foo:
-    image: busybox
-`)
-	assert.NilError(t, err)
-}
-
-func TestUnsupportedVersion(t *testing.T) {
-	_, err := loadYAML(`
-version: "2"
-services:
-  foo:
-    image: busybox
-`)
-	assert.ErrorContains(t, err, "version")
-
-	_, err = loadYAML(`
-version: "2.0"
-services:
-  foo:
-    image: busybox
-`)
-	assert.ErrorContains(t, err, "version")
-}
-
-func TestInvalidVersion(t *testing.T) {
-	_, err := loadYAML(`
-version: 3
-services:
-  foo:
-    image: busybox
-`)
-	assert.ErrorContains(t, err, "version must be a string")
-}
-
 func TestV1Unsupported(t *testing.T) {
 	_, err := loadYAML(`
 foo:
   image: busybox
 `)
-	assert.ErrorContains(t, err, "unsupported Compose file version: 1.0")
+	assert.Check(t, err != nil)
 }
 
 func TestNonMappingObject(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   - foo:
       image: busybox
@@ -421,14 +383,12 @@ services:
 	assert.ErrorContains(t, err, "services must be a mapping")
 
 	_, err = loadYAML(`
-version: "3"
 services:
   foo: busybox
 `)
 	assert.ErrorContains(t, err, "services.foo must be a mapping")
 
 	_, err = loadYAML(`
-version: "3"
 networks:
   - default:
       driver: bridge
@@ -436,14 +396,12 @@ networks:
 	assert.ErrorContains(t, err, "networks must be a mapping")
 
 	_, err = loadYAML(`
-version: "3"
 networks:
   default: bridge
 `)
 	assert.ErrorContains(t, err, "networks.default must be a mapping")
 
 	_, err = loadYAML(`
-version: "3"
 volumes:
   - data:
       driver: local
@@ -451,7 +409,6 @@ volumes:
 	assert.ErrorContains(t, err, "volumes must be a mapping")
 
 	_, err = loadYAML(`
-version: "3"
 volumes:
   data: local
 `)
@@ -460,7 +417,6 @@ volumes:
 
 func TestNonStringImage(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   foo:
     image: ["busybox", "latest"]
@@ -470,7 +426,6 @@ services:
 
 func TestLoadWithEnvironment(t *testing.T) {
 	config, err := loadYAMLWithEnv(`
-version: "3"
 services:
   dict-env:
     image: busybox
@@ -508,7 +463,6 @@ services:
 
 func TestInvalidEnvironmentValue(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   dict-env:
     image: busybox
@@ -520,7 +474,6 @@ services:
 
 func TestInvalidEnvironmentObject(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   dict-env:
     image: busybox
@@ -532,7 +485,6 @@ services:
 func TestLoadWithEnvironmentInterpolation(t *testing.T) {
 	home := "/home/foo"
 	config, err := loadYAMLWithEnv(`
-version: "3"
 services:
   test:
     image: busybox
@@ -568,7 +520,6 @@ volumes:
 
 func TestLoadWithInterpolationCastFull(t *testing.T) {
 	dict, err := ParseYAML([]byte(`
-version: "3"
 services:
   web:
     configs:
@@ -731,7 +682,6 @@ networks:
 
 func TestUnsupportedProperties(t *testing.T) {
 	dict, err := ParseYAML([]byte(`
-version: "3"
 services:
   web:
     image: web
@@ -754,8 +704,7 @@ services:
 }
 
 func TestDiscardEnvFileOption(t *testing.T) {
-	dict, err := ParseYAML([]byte(`version: "3"
-services:
+	dict, err := ParseYAML([]byte(`services:
   web:
     image: nginx
     env_file:
@@ -787,7 +736,6 @@ services:
 
 func TestBuildProperties(t *testing.T) {
 	dict, err := ParseYAML([]byte(`
-version: "3"
 services:
   web:
     image: web
@@ -807,7 +755,6 @@ services:
 
 func TestDeprecatedProperties(t *testing.T) {
 	dict, err := ParseYAML([]byte(`
-version: "3"
 services:
   web:
     image: web
@@ -827,7 +774,6 @@ services:
 
 func TestInvalidResource(t *testing.T) {
 	_, err := loadYAML(`
-        version: "3"
         services:
           foo:
             image: busybox
@@ -841,7 +787,6 @@ func TestInvalidResource(t *testing.T) {
 
 func TestInvalidExternalAndDriverCombination(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 volumes:
   external_volume:
     external: true
@@ -854,7 +799,6 @@ volumes:
 
 func TestInvalidExternalAndDirverOptsCombination(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 volumes:
   external_volume:
     external: true
@@ -868,7 +812,6 @@ volumes:
 
 func TestInvalidExternalAndLabelsCombination(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 volumes:
   external_volume:
     external: true
@@ -882,7 +825,6 @@ volumes:
 
 func TestLoadVolumeInvalidExternalNameAndNameCombination(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 volumes:
   external_volume:
     name: user_specified_name
@@ -932,7 +874,6 @@ func TestFullExample(t *testing.T) {
 
 func TestLoadTmpfsVolume(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   tmpfs:
     image: nginx:latest
@@ -959,7 +900,6 @@ services:
 
 func TestLoadTmpfsVolumeAdditionalPropertyNotAllowed(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   tmpfs:
     image: nginx:latest
@@ -974,7 +914,6 @@ services:
 
 func TestLoadBindMountSourceMustNotBeEmpty(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   tmpfs:
     image: nginx:latest
@@ -994,8 +933,6 @@ func TestLoadBindMountSourceIsWindowsAbsolute(t *testing.T) {
 		{
 			doc: "Z-drive lowercase",
 			yaml: `
-version: "3"
-
 services:
   windows:
     image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
@@ -1009,8 +946,6 @@ services:
 		{
 			doc: "Z-drive uppercase",
 			yaml: `
-version: "3"
-
 services:
   windows:
     image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
@@ -1024,8 +959,6 @@ services:
 		{
 			doc: "Z-drive subdirectory",
 			yaml: `
-version: "3"
-
 services:
   windows:
     image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
@@ -1039,8 +972,6 @@ services:
 		{
 			doc: "forward-slashes",
 			yaml: `
-version: "3"
-
 services:
   app:
     image: app:latest
@@ -1065,7 +996,6 @@ services:
 
 func TestLoadBindMountWithSource(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   bind:
     image: nginx:latest
@@ -1092,7 +1022,6 @@ services:
 
 func TestLoadTmpfsVolumeSizeCanBeZero(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   tmpfs:
     image: nginx:latest
@@ -1117,7 +1046,6 @@ services:
 
 func TestLoadTmpfsVolumeSizeMustBeGTEQZero(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   tmpfs:
     image: nginx:latest
@@ -1132,7 +1060,6 @@ services:
 
 func TestLoadTmpfsVolumeSizeMustBeInteger(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 services:
   tmpfs:
     image: nginx:latest
@@ -1154,7 +1081,6 @@ func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
 
 func TestLoadAttachableNetwork(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 networks:
   mynet1:
     driver: overlay
@@ -1180,7 +1106,6 @@ networks:
 
 func TestLoadExpandedPortFormat(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   web:
     image: busybox
@@ -1204,7 +1129,6 @@ services:
 
 func TestLoadExpandedMountFormat(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   web:
     image: busybox
@@ -1232,7 +1156,6 @@ volumes:
 
 func TestLoadExtraHostsMap(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   web:
     image: busybox
@@ -1253,7 +1176,6 @@ services:
 
 func TestLoadExtraHostsList(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   web:
     image: busybox
@@ -1331,7 +1253,6 @@ func TestLoadVolumesWarnOnDeprecatedExternalName(t *testing.T) {
 func TestLoadInvalidIsolation(t *testing.T) {
 	// validation should be done only on the daemon side
 	actual, err := loadYAML(`
-version: "3"
 services:
   foo:
     image: busybox
@@ -1347,7 +1268,6 @@ configs:
 
 func TestLoadSecretInvalidExternalNameAndNameCombination(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 secrets:
   external_secret:
     name: user_specified_name
@@ -1370,9 +1290,7 @@ func TestLoadSecretsWarnOnDeprecatedExternalNameVersion35(t *testing.T) {
 			},
 		},
 	}
-	details := types.ConfigDetails{
-		Version: "3.5",
-	}
+	details := types.ConfigDetails{}
 	secrets, err := LoadSecrets(source, details)
 	assert.NilError(t, err)
 	expected := map[string]types.SecretConfig{
@@ -1434,7 +1352,6 @@ func TestLoadNetworksWarnOnDeprecatedExternalName(t *testing.T) {
 
 func TestLoadNetworkInvalidExternalNameAndNameCombination(t *testing.T) {
 	_, err := loadYAML(`
-version: "3"
 networks:
   foo:
     name: user_specified_name
@@ -1448,7 +1365,6 @@ networks:
 
 func TestLoadNetworkWithName(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   hello-world:
     image: redis:alpine
@@ -1498,7 +1414,6 @@ func TestLoadInit(t *testing.T) {
 		{
 			doc: "no init defined",
 			yaml: `
-version: "3"
 services:
   foo:
     image: alpine`,
@@ -1506,7 +1421,6 @@ services:
 		{
 			doc: "has true init",
 			yaml: `
-version: "3"
 services:
   foo:
     image: alpine
@@ -1516,7 +1430,6 @@ services:
 		{
 			doc: "has false init",
 			yaml: `
-version: "3"
 services:
   foo:
     image: alpine
@@ -1537,7 +1450,6 @@ services:
 
 func TestLoadSysctls(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   web:
     image: busybox
@@ -1560,7 +1472,6 @@ services:
 	assert.Check(t, is.DeepEqual(expected, config.Services[0].Sysctls))
 
 	config, err = loadYAML(`
-version: "3"
 services:
   web:
     image: busybox
@@ -1602,7 +1513,6 @@ func TestTransform(t *testing.T) {
 
 func TestLoadTemplateDriver(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   hello-world:
     image: redis:alpine
@@ -1667,7 +1577,6 @@ secrets:
 
 func TestLoadSecretDriver(t *testing.T) {
 	config, err := loadYAML(`
-version: "3"
 services:
   hello-world:
     image: redis:alpine
diff --git a/loader/merge_test.go b/loader/merge_test.go
index 4e686c9..00e4415 100644
--- a/loader/merge_test.go
+++ b/loader/merge_test.go
@@ -26,23 +26,6 @@ import (
 	"gotest.tools/v3/assert"
 )
 
-/* FIXME to be reconsidered once we have new schema versions
-func TestLoadTwoDifferentVersion(t *testing.T) {
-	configDetails := types.ConfigDetails{
-		ConfigFiles: []types.ConfigFile{
-			{Filename: "base.yml", Config: map[string]interface{}{
-				"version": "3.1",
-			}},
-			{Filename: "override.yml", Config: map[string]interface{}{
-				"version": "3.4",
-			}},
-		},
-	}
-	_, err := Load(configDetails)
-	assert.Error(t, err, "version mismatched between two composefiles : 3.1 and 3.4")
-}
-*/
-
 func TestLoadLogging(t *testing.T) {
 	loggingCases := []struct {
 		name            string
@@ -207,7 +190,6 @@ func TestLoadLogging(t *testing.T) {
 					{
 						Filename: "base.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.loggingBase,
 							},
@@ -216,7 +198,6 @@ func TestLoadLogging(t *testing.T) {
 					{
 						Filename: "override.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.loggingOverride,
 							},
@@ -333,7 +314,6 @@ func TestLoadMultipleServicePorts(t *testing.T) {
 					{
 						Filename: "base.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.portBase,
 							},
@@ -342,7 +322,6 @@ func TestLoadMultipleServicePorts(t *testing.T) {
 					{
 						Filename: "override.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.portOverride,
 							},
@@ -459,7 +438,6 @@ func TestLoadMultipleSecretsConfig(t *testing.T) {
 					{
 						Filename: "base.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.secretBase,
 							},
@@ -468,7 +446,6 @@ func TestLoadMultipleSecretsConfig(t *testing.T) {
 					{
 						Filename: "override.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.secretOverride,
 							},
@@ -585,7 +562,6 @@ func TestLoadMultipleConfigobjsConfig(t *testing.T) {
 					{
 						Filename: "base.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.configBase,
 							},
@@ -594,7 +570,6 @@ func TestLoadMultipleConfigobjsConfig(t *testing.T) {
 					{
 						Filename: "override.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.configOverride,
 							},
@@ -701,7 +676,6 @@ func TestLoadMultipleUlimits(t *testing.T) {
 					{
 						Filename: "base.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.ulimitBase,
 							},
@@ -710,7 +684,6 @@ func TestLoadMultipleUlimits(t *testing.T) {
 					{
 						Filename: "override.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.ulimitOverride,
 							},
@@ -820,7 +793,6 @@ func TestLoadMultipleServiceNetworks(t *testing.T) {
 					{
 						Filename: "base.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.networkBase,
 							},
@@ -829,7 +801,6 @@ func TestLoadMultipleServiceNetworks(t *testing.T) {
 					{
 						Filename: "override.yml",
 						Config: map[string]interface{}{
-							"version": "3",
 							"services": map[string]interface{}{
 								"foo": tc.networkOverride,
 							},
@@ -860,7 +831,6 @@ func TestLoadMultipleServiceNetworks(t *testing.T) {
 
 func TestLoadMultipleConfigs(t *testing.T) {
 	base := map[string]interface{}{
-		"version": "3",
 		"services": map[string]interface{}{
 			"foo": map[string]interface{}{
 				"image": "foo",
@@ -886,7 +856,6 @@ func TestLoadMultipleConfigs(t *testing.T) {
 		"configs":  map[string]interface{}{},
 	}
 	override := map[string]interface{}{
-		"version": "3",
 		"services": map[string]interface{}{
 			"foo": map[string]interface{}{
 				"image": "baz",
@@ -975,7 +944,6 @@ func TestLoadMultipleConfigs(t *testing.T) {
 // Issue#972
 func TestLoadMultipleNetworks(t *testing.T) {
 	base := map[string]interface{}{
-		"version": "3",
 		"services": map[string]interface{}{
 			"foo": map[string]interface{}{
 				"image": "baz",
@@ -999,7 +967,6 @@ func TestLoadMultipleNetworks(t *testing.T) {
 		"configs": map[string]interface{}{},
 	}
 	override := map[string]interface{}{
-		"version":  "3",
 		"services": map[string]interface{}{},
 		"volumes":  map[string]interface{}{},
 		"networks": map[string]interface{}{
diff --git a/loader/types_test.go b/loader/types_test.go
index 61b2f15..c88b1ff 100644
--- a/loader/types_test.go
+++ b/loader/types_test.go
@@ -39,7 +39,7 @@ func TestMarshallConfig(t *testing.T) {
 	assert.Check(t, is.Equal(expected, string(actual)))
 
 	// Make sure the expected still
-	dict, err := ParseYAML([]byte("version: '3.7'\n" + expected))
+	dict, err := ParseYAML([]byte(expected))
 	assert.NilError(t, err)
 	_, err = Load(buildConfigDetails(dict, map[string]string{}), func(options *Options) {
 		options.SkipNormalization = true
diff --git a/schema/schema_test.go b/schema/schema_test.go
index 2e0a521..8467cde 100644
--- a/schema/schema_test.go
+++ b/schema/schema_test.go
@@ -17,7 +17,6 @@
 package schema
 
 import (
-	"fmt"
 	"testing"
 
 	"gotest.tools/v3/assert"
@@ -27,7 +26,6 @@ type dict map[string]interface{}
 
 func TestValidate(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -35,12 +33,11 @@ func TestValidate(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateUndefinedTopLevelOption(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"helicopters": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -48,23 +45,21 @@ func TestValidateUndefinedTopLevelOption(t *testing.T) {
 		},
 	}
 
-	err := Validate(config, "3.9")
+	err := Validate(config)
 	assert.ErrorContains(t, err, "Additional property helicopters is not allowed")
 }
 
 func TestValidateAllowsXTopLevelFields(t *testing.T) {
 	config := dict{
-		"version":       "3",
 		"x-extra-stuff": dict{},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateAllowsXFields(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"bar": dict{
 				"x-extra-stuff": dict{},
@@ -91,46 +86,12 @@ func TestValidateAllowsXFields(t *testing.T) {
 			},
 		},
 	}
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
-}
-
-func TestValidateCredentialSpecs(t *testing.T) {
-	tests := []struct {
-		version     string
-		expectedErr string
-	}{
-		{version: "3"},
-		{version: "3.9"},
-	}
-
-	for _, tc := range tests {
-		tc := tc
-		t.Run(tc.version, func(t *testing.T) {
-			config := dict{
-				"version": "99.99",
-				"services": dict{
-					"foo": dict{
-						"image": "busybox",
-						"credential_spec": dict{
-							"config": "foobar",
-						},
-					},
-				},
-			}
-			err := Validate(config, tc.version)
-			if tc.expectedErr != "" {
-				assert.ErrorContains(t, err, fmt.Sprintf("Additional property %s is not allowed", tc.expectedErr))
-			} else {
-				assert.NilError(t, err)
-			}
-		})
-	}
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateSecretConfigNames(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"configs": dict{
 			"bar": dict{
 				"name": "foobar",
@@ -143,29 +104,14 @@ func TestValidateSecretConfigNames(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
-}
-
-func TestValidateInvalidVersion(t *testing.T) {
-	config := dict{
-		"version": "2.1",
-		"services": dict{
-			"foo": dict{
-				"image": "busybox",
-			},
-		},
-	}
-
-	err := Validate(config, "2.1")
-	assert.ErrorContains(t, err, "unsupported Compose file version: 2.1")
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 type array []interface{}
 
 func TestValidatePlacement(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -182,13 +128,12 @@ func TestValidatePlacement(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateIsolation(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image":     "busybox",
@@ -196,13 +141,12 @@ func TestValidateIsolation(t *testing.T) {
 			},
 		},
 	}
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateRollbackConfig(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -215,13 +159,12 @@ func TestValidateRollbackConfig(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateRollbackConfigWithOrder(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -235,13 +178,12 @@ func TestValidateRollbackConfigWithOrder(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateRollbackConfigWithUpdateConfig(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -259,13 +201,12 @@ func TestValidateRollbackConfigWithUpdateConfig(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }
 
 func TestValidateRollbackConfigWithUpdateConfigFull(t *testing.T) {
 	config := dict{
-		"version": "3",
 		"services": dict{
 			"foo": dict{
 				"image": "busybox",
@@ -289,6 +230,6 @@ func TestValidateRollbackConfigWithUpdateConfigFull(t *testing.T) {
 		},
 	}
 
-	assert.NilError(t, Validate(config, "3"))
-	assert.NilError(t, Validate(config, "3.9"))
+	assert.NilError(t, Validate(config))
+	assert.NilError(t, Validate(config))
 }