diff --git a/docs/reference/config.md b/docs/reference/config.md
index 20be947a59..78512f08f7 100644
--- a/docs/reference/config.md
+++ b/docs/reference/config.md
@@ -29,7 +29,7 @@ The schema version of this project's config (currently not used).
 
 | Type | Required | Allowed Values |
 | ---- | -------- | -------------- |
-| `string` | Yes | "0"
+| `string` | Yes | "garden.io/v0"
 ### `project.name`
 [project](#project) > name
 
@@ -207,7 +207,7 @@ project:
 ## Project YAML schema
 ```yaml
 project:
-  apiVersion: '0'
+  apiVersion: garden.io/v0
   name:
   defaultEnvironment: ''
   environmentDefaults:
@@ -240,7 +240,7 @@ The schema version of this module's config (currently not used).
 
 | Type | Required | Allowed Values |
 | ---- | -------- | -------------- |
-| `string` | Yes | "0"
+| `string` | Yes | "garden.io/v0"
 ### `module.type`
 [module](#module) > type
 
@@ -388,7 +388,7 @@ POSIX-style path or filename to copy the directory or file(s) to (defaults to sa
 ## Module YAML schema
 ```yaml
 module:
-  apiVersion: '0'
+  apiVersion: garden.io/v0
   type:
   name:
   description:
diff --git a/garden-service/src/cli/cli.ts b/garden-service/src/cli/cli.ts
index 31b798e941..9240961328 100644
--- a/garden-service/src/cli/cli.ts
+++ b/garden-service/src/cli/cli.ts
@@ -73,7 +73,7 @@ export const MOCK_CONFIG: GardenConfig = {
   dirname: "/",
   path: process.cwd(),
   project: {
-    apiVersion: "0",
+    apiVersion: "garden.io/v0",
     name: "mock-project",
     defaultEnvironment: "local",
     environments: defaultEnvironments,
diff --git a/garden-service/src/config/base.ts b/garden-service/src/config/base.ts
index 1693e5253c..aa2e353248 100644
--- a/garden-service/src/config/base.ts
+++ b/garden-service/src/config/base.ts
@@ -6,7 +6,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-import { join, basename, sep, resolve } from "path"
+import { join, basename, sep, resolve, relative } from "path"
 import {
   findByName,
   getNames,
@@ -87,10 +87,91 @@ type ConfigDoc = {
   project?: ProjectConfig,
 }
 
+export type ConfigKind = "Module" | "Project"
+export const configKinds = new Set(["Module", "Project"])
+
+const configKindSettings = {
+  Module: {
+    specKey: "module",
+    validationSchema: baseModuleSpecSchema,
+  },
+  Project: {
+    specKey: "project",
+    validationSchema: projectSchema,
+  },
+}
+
 /**
  * Each YAML document in a garden.yml file consists of a project definition and/or a module definition.
+ *
+ * A document can be structured according to either the (old) nested or the (new) flat style.
+ *
+ * In the nested style, the project/module's config is nested under the project/module key respectively.
+ *
+ * In the flat style, the project/module's config is at the top level, and the kind key is used to indicate
+ * whether the entity being configured is a project or a module (similar to the YAML syntax for k8s object
+ * definitions). The kind key is removed before validation, so that specs following both styles can be validated
+ * with the same schema.
  */
 function prepareConfigDoc(spec: any, path: string, projectRoot: string): ConfigDoc {
+
+  const kind = spec.kind
+
+  if (!spec.kind) {
+    const preparedSpec = prepareScopedConfigDoc(spec, path)
+    // validate with scoped config schema
+    return validateWithPath({
+      config: preparedSpec,
+      schema: configSchema,
+      configType: "config",
+      path,
+      projectRoot,
+    })
+  }
+
+  if (configKinds.has(kind)) {
+    const { specKey, validationSchema } = configKindSettings[kind]
+    delete spec.kind
+    const preparedSpec = prepareFlatConfigDoc(spec, path)
+    const validated = validateWithPath({
+      config: preparedSpec,
+      schema: validationSchema,
+      configType: specKey,
+      path,
+      projectRoot,
+    })
+    return { [specKey]: validated }
+  } else {
+    const relPath = `${relative(projectRoot, path)}/garden.yml`
+    throw new ConfigurationError(`Unknown config kind ${kind} in ${relPath}`, { kind, path: relPath })
+  }
+
+}
+
+/**
+ * The new / flat configuration style.
+ *
+ * The spec defines either a project or a module (determined by its "kind" field).
+ */
+function prepareFlatConfigDoc(spec: any, path: string): ConfigDoc {
+  if (spec.kind === "Project") {
+    spec = prepareProjectConfig(spec, path)
+  }
+
+  if (spec.kind === "Module") {
+    spec = prepareModuleConfig(spec, path)
+  }
+
+  return spec
+}
+
+/**
+ * The old / nested configuration style.
+ *
+ * The spec defines a project and/or a module, with the config for each nested under the "project" / "module" field,
+ * respectively.
+ */
+function prepareScopedConfigDoc(spec: any, path: string): ConfigDoc {
   if (spec.project) {
     spec.project = prepareProjectConfig(spec.project, path)
   }
@@ -99,13 +180,7 @@ function prepareConfigDoc(spec: any, path: string, projectRoot: string): ConfigD
     spec.module = prepareModuleConfig(spec.module, path)
   }
 
-  return validateWithPath({
-    config: spec,
-    schema: configSchema,
-    configType: "config",
-    path,
-    projectRoot,
-  })
+  return spec
 }
 
 function prepareProjectConfig(projectSpec: any, path: string): ProjectConfig {
diff --git a/garden-service/src/config/module.ts b/garden-service/src/config/module.ts
index 443466536a..4565974795 100644
--- a/garden-service/src/config/module.ts
+++ b/garden-service/src/config/module.ts
@@ -78,8 +78,8 @@ export interface BaseModuleSpec {
 export const baseModuleSpecSchema = Joi.object()
   .keys({
     apiVersion: Joi.string()
-      .default("0")
-      .only("0")
+      .default("garden.io/v0")
+      .only("garden.io/v0")
       .description("The schema version of this module's config (currently not used)."),
     type: joiIdentifier()
       .required()
diff --git a/garden-service/src/config/project.ts b/garden-service/src/config/project.ts
index c2b9984a99..283bdaae94 100644
--- a/garden-service/src/config/project.ts
+++ b/garden-service/src/config/project.ts
@@ -124,8 +124,8 @@ export const projectNameSchema = joiIdentifier()
 export const projectSchema = Joi.object()
   .keys({
     apiVersion: Joi.string()
-      .default("0")
-      .only("0")
+      .default("garden.io/v0")
+      .only("garden.io/v0")
       .description("The schema version of this project's config (currently not used)."),
     name: projectNameSchema,
     defaultEnvironment: Joi.string()
diff --git a/garden-service/src/plugins/kubernetes/system.ts b/garden-service/src/plugins/kubernetes/system.ts
index 6619cccf67..7b870be179 100644
--- a/garden-service/src/plugins/kubernetes/system.ts
+++ b/garden-service/src/plugins/kubernetes/system.ts
@@ -27,7 +27,7 @@ export async function getSystemGarden(provider: KubernetesProvider): Promise<Gar
       dirname: "system",
       path: systemProjectPath,
       project: {
-        apiVersion: "0",
+        apiVersion: "garden.io/v0",
         name: "garden-system",
         environmentDefaults: {
           providers: [],
diff --git a/garden-service/src/plugins/local/local-google-cloud-functions.ts b/garden-service/src/plugins/local/local-google-cloud-functions.ts
index cc4e72cb86..15538f9746 100644
--- a/garden-service/src/plugins/local/local-google-cloud-functions.ts
+++ b/garden-service/src/plugins/local/local-google-cloud-functions.ts
@@ -76,7 +76,7 @@ export const gardenPlugin = (): GardenPlugin => ({
         })
 
         return {
-          apiVersion: "0",
+          apiVersion: "garden.io/v0",
           allowPublish: true,
           build: {
             command: [],
diff --git a/garden-service/src/plugins/openfaas/openfaas.ts b/garden-service/src/plugins/openfaas/openfaas.ts
index 06a9ec04e5..cca52b8658 100644
--- a/garden-service/src/plugins/openfaas/openfaas.ts
+++ b/garden-service/src/plugins/openfaas/openfaas.ts
@@ -467,7 +467,7 @@ export async function getOpenFaasGarden(ctx: PluginContext): Promise<Garden> {
       dirname: "system",
       path: systemProjectPath,
       project: {
-        apiVersion: "0",
+        apiVersion: "garden.io/v0",
         name: `${ctx.projectName}-openfaas`,
         environmentDefaults: {
           providers: [],
diff --git a/garden-service/test/data/test-project-flat-config/garden.yml b/garden-service/test/data/test-project-flat-config/garden.yml
new file mode 100644
index 0000000000..acb8753c39
--- /dev/null
+++ b/garden-service/test/data/test-project-flat-config/garden.yml
@@ -0,0 +1,19 @@
+kind: Project
+name: test-project-flat-config
+environmentDefaults:
+  variables:
+    some: variable
+environments:
+  - name: local
+    providers:
+      - name: test-plugin
+      - name: test-plugin-b
+  - name: other
+
+---
+
+kind: Module
+name: module-from-project-config
+type: test
+build:
+  command: [echo, project]
\ No newline at end of file
diff --git a/garden-service/test/data/test-project-flat-config/invalid-config-kind/.garden-version b/garden-service/test/data/test-project-flat-config/invalid-config-kind/.garden-version
new file mode 100644
index 0000000000..1d754b7141
--- /dev/null
+++ b/garden-service/test/data/test-project-flat-config/invalid-config-kind/.garden-version
@@ -0,0 +1,4 @@
+{
+  "latestCommit": "1234567890",
+  "dirtyTimestamp": null
+}
diff --git a/garden-service/test/data/test-project-flat-config/invalid-config-kind/garden.yml b/garden-service/test/data/test-project-flat-config/invalid-config-kind/garden.yml
new file mode 100644
index 0000000000..635faf42aa
--- /dev/null
+++ b/garden-service/test/data/test-project-flat-config/invalid-config-kind/garden.yml
@@ -0,0 +1,15 @@
+kind: Banana
+name: module-a1
+type: test
+services:
+  - name: service-a1
+build:
+  command: [echo, A1]
+  dependencies:
+    - module-from-project-config
+tests:
+  - name: unit
+    command: [echo, OK]
+tasks:
+  - name: task-a1
+    command: [echo, OK]
\ No newline at end of file
diff --git a/garden-service/test/helpers.ts b/garden-service/test/helpers.ts
index fa00dfe986..8c5db9c021 100644
--- a/garden-service/test/helpers.ts
+++ b/garden-service/test/helpers.ts
@@ -220,7 +220,7 @@ export const testPluginC: PluginFactory = async (params) => {
 }
 
 const defaultModuleConfig: ModuleConfig = {
-  apiVersion: "0",
+  apiVersion: "garden.io/v0",
   type: "test",
   name: "test",
   path: "bla",
diff --git a/garden-service/test/src/config/base.ts b/garden-service/test/src/config/base.ts
index 6aa9de2f42..4f510c16dd 100644
--- a/garden-service/test/src/config/base.ts
+++ b/garden-service/test/src/config/base.ts
@@ -11,6 +11,9 @@ const modulePathAMultiple = resolve(projectPathMultipleModules, "module-a")
 
 const projectPathDuplicateProjects = resolve(dataDir, "test-project-duplicate-project-config")
 
+const projectPathFlat = resolve(dataDir, "test-project-flat-config")
+const modulePathFlatInvalid = resolve(projectPathFlat, "invalid-config-kind")
+
 describe("loadConfig", () => {
 
   it("should not throw an error if no file was found", async () => {
@@ -19,7 +22,7 @@ describe("loadConfig", () => {
     expect(parsed).to.eql(undefined)
   })
 
-  it("should throw a config error if the file couldn't be parsed°", async () => {
+  it("should throw a config error if the file couldn't be parsed", async () => {
     const projectPath = resolve(dataDir, "test-project-invalid-config")
     await expectError(
       async () => await loadConfig(projectPath, resolve(projectPath, "invalid-syntax-module")),
@@ -42,7 +45,7 @@ describe("loadConfig", () => {
     const parsed = await loadConfig(projectPathA, projectPathA)
 
     expect(parsed!.project).to.eql({
-      apiVersion: "0",
+      apiVersion: "garden.io/v0",
       name: "test-project-a",
       defaultEnvironment: "local",
       sources: [],
@@ -73,7 +76,7 @@ describe("loadConfig", () => {
 
     expect(parsed!.modules).to.eql([
       {
-        apiVersion: "0",
+        apiVersion: "garden.io/v0",
         name: "module-a",
         type: "test",
         description: undefined,
@@ -106,7 +109,7 @@ describe("loadConfig", () => {
     const parsed = await loadConfig(projectPathMultipleModules, projectPathMultipleModules)
 
     expect(parsed!.project).to.eql({
-      apiVersion: "0",
+      apiVersion: "garden.io/v0",
       defaultEnvironment: "local",
       environmentDefaults: {
         providers: [],
@@ -134,7 +137,7 @@ describe("loadConfig", () => {
     })
 
     expect(parsed!.modules).to.eql([{
-      apiVersion: "0",
+      apiVersion: "garden.io/v0",
       name: "module-from-project-config",
       type: "test",
       description: undefined,
@@ -155,7 +158,7 @@ describe("loadConfig", () => {
 
     expect(parsed!.modules).to.eql([
       {
-        apiVersion: "0",
+        apiVersion: "garden.io/v0",
         name: "module-a1",
         type: "test",
         allowPublish: true,
@@ -179,7 +182,7 @@ describe("loadConfig", () => {
         taskConfigs: [],
       },
       {
-        apiVersion: "0",
+        apiVersion: "garden.io/v0",
         name: "module-a2",
         type: "test",
         allowPublish: true,
@@ -200,6 +203,55 @@ describe("loadConfig", () => {
     ])
   })
 
+  it("should parse a config file using the flat config style", async () => {
+    const parsed = await loadConfig(projectPathFlat, projectPathFlat)
+
+    expect(parsed!.project).to.eql({
+      apiVersion: "garden.io/v0",
+      defaultEnvironment: "",
+      environmentDefaults: {
+        providers: [],
+        variables: { some: "variable" },
+      },
+      environments: [
+        {
+          name: "local",
+          providers: [
+            { name: "test-plugin" },
+            { name: "test-plugin-b" },
+          ],
+          variables: {},
+        },
+        {
+          name: "other",
+          providers: [],
+          variables: {},
+        },
+      ],
+      name: "test-project-flat-config",
+      sources: [],
+    })
+
+    expect(parsed!.modules).to.eql([{
+      name: "module-from-project-config",
+      type: "test",
+      build: {
+        command: ["echo", "project"],
+        dependencies: [],
+      },
+      apiVersion: "garden.io/v0",
+      allowPublish: true,
+    }])
+  })
+
+  it("should throw an error when parsing a flat-style config using an unknown/invalid kind", async () => {
+    await expectError(
+      async () => await loadConfig(projectPathFlat, modulePathFlatInvalid),
+      (err) => {
+        expect(err.message).to.match(/Unknown config kind/)
+      })
+  })
+
   it("should throw an error when parsing a config file defining multiple projects", async () => {
     await expectError(
       async () => await loadConfig(projectPathDuplicateProjects, projectPathDuplicateProjects),
diff --git a/garden-service/test/src/plugins/container.ts b/garden-service/test/src/plugins/container.ts
index e8a51a65b1..0105fa650e 100644
--- a/garden-service/test/src/plugins/container.ts
+++ b/garden-service/test/src/plugins/container.ts
@@ -34,7 +34,7 @@ describe("plugins.container", () => {
       command: [],
       dependencies: [],
     },
-    apiVersion: "0",
+    apiVersion: "garden.io/v0",
     name: "test",
     outputs: {},
     path: modulePath,
@@ -137,7 +137,7 @@ describe("plugins.container", () => {
           dependencies: [],
         },
         name: "test",
-        apiVersion: "0",
+        apiVersion: "garden.io/v0",
         outputs: {},
         path: modulePath,
         type: "container",
@@ -191,7 +191,7 @@ describe("plugins.container", () => {
             command: ["echo", "OK"],
             dependencies: [],
           },
-          apiVersion: "0",
+          apiVersion: "garden.io/v0",
           name: "module-a",
           outputs: {},
           path: modulePath,
@@ -253,7 +253,7 @@ describe("plugins.container", () => {
         expect(result).to.eql({
           allowPublish: false,
           build: { command: ["echo", "OK"], dependencies: [] },
-          apiVersion: "0",
+          apiVersion: "garden.io/v0",
           name: "module-a",
           outputs: {},
           path: modulePath,
@@ -370,7 +370,7 @@ describe("plugins.container", () => {
             command: ["echo", "OK"],
             dependencies: [],
           },
-          apiVersion: "0",
+          apiVersion: "garden.io/v0",
           name: "module-a",
           outputs: {},
           path: modulePath,
@@ -427,7 +427,7 @@ describe("plugins.container", () => {
             command: ["echo", "OK"],
             dependencies: [],
           },
-          apiVersion: "0",
+          apiVersion: "garden.io/v0",
           name: "module-a",
           outputs: {},
           path: modulePath,
@@ -479,7 +479,7 @@ describe("plugins.container", () => {
             command: ["echo", "OK"],
             dependencies: [],
           },
-          apiVersion: "0",
+          apiVersion: "garden.io/v0",
           name: "module-a",
           outputs: {},
           path: modulePath,
diff --git a/garden-service/test/src/plugins/kubernetes/container/ingress.ts b/garden-service/test/src/plugins/kubernetes/container/ingress.ts
index 558573a421..25195057ea 100644
--- a/garden-service/test/src/plugins/kubernetes/container/ingress.ts
+++ b/garden-service/test/src/plugins/kubernetes/container/ingress.ts
@@ -336,7 +336,7 @@ describe("createIngresses", () => {
         command: [],
         dependencies: [],
       },
-      apiVersion: "0",
+      apiVersion: "garden.io/v0",
       name: "test",
       outputs: {},
       path: "/tmp",
diff --git a/garden-service/test/src/plugins/kubernetes/helm/config.ts b/garden-service/test/src/plugins/kubernetes/helm/config.ts
index a7d3bd626e..6ad3a479aa 100644
--- a/garden-service/test/src/plugins/kubernetes/helm/config.ts
+++ b/garden-service/test/src/plugins/kubernetes/helm/config.ts
@@ -43,7 +43,7 @@ describe("validateHelmModule", () => {
         command: [],
       },
       description: "The API backend for the voting UI",
-      apiVersion: "0",
+      apiVersion: "garden.io/v0",
       name: "api",
       outputs: {
         "release-name": "api-release",