{"record":{"id":"a66dc5a71e0122f0","repo":"louislam/dockge","slug":"services-must-be-an-object","errorCode":null,"errorMessage":"Services must be an object","messagePattern":"Services must be an object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/src/pages/Compose.vue","lineNumber":735,"sourceCode":"            this.isEditMode = false;\n        },\n\n        yamlToJSON(yaml) {\n            let doc = parseDocument(yaml);\n            if (doc.errors.length > 0) {\n                throw doc.errors[0];\n            }\n\n            const config = doc.toJS() ?? {};\n\n            // Check data types\n            // \"services\" must be an object\n            if (!config.services) {\n                config.services = {};\n            }\n\n            if (Array.isArray(config.services) || typeof config.services !== \"object\") {\n                throw new Error(\"Services must be an object\");\n            }\n\n            return {\n                config,\n                doc,\n            };\n        },\n\n        yamlCodeChange() {\n            try {\n                let { config, doc } = this.yamlToJSON(this.stack.composeYAML);\n\n                this.yamlDoc = doc;\n                this.jsonConfig = config;\n\n                let env = dotenv.parse(this.stack.composeENV);\n                let envYAML = envsubstYAML(this.stack.composeYAML, env);\n                this.envsubstJSONConfig = this.yamlToJSON(envYAML).config;","sourceCodeStart":717,"sourceCodeEnd":753,"githubUrl":"https://github.com/louislam/dockge/blob/f809ae192b571944ad773e9866d3e67064ae8043/frontend/src/pages/Compose.vue#L717-L753","documentation":"In frontend/src/pages/Compose.vue, when parsing the compose YAML the code ensures `config.services` exists and then validates that it is a non-array object. If yaml-js parses `services:` as something other than a mapping (an array, a string, a number) or it is missing entirely in a way that can't be defaulted, the parser throws 'Services must be an object'. This enforces the Docker Compose schema requirement that services is a mapping of service name -> definition.","triggerScenarios":"Editing the compose editor content so that `services:` is absent, or written as a list (`services: - web`), or as a scalar (`services: web`), then triggering save/parse of the stack.","commonSituations":"Hand-edited YAML with wrong indentation causing services to parse as something unexpected; users pasting a `docker run` style file or plain YAML without a services key; accidentally deleting the services block while editing.","solutions":["Add a top-level `services:` mapping to the YAML with at least one service","Fix indentation so each service is a named key under services (a mapping, not a list)","Validate with `docker compose config` or a YAML linter before saving","If services is intentionally empty, write `services: {}` explicitly"],"exampleFix":"// before (invalid)\nservices:\n  - web\n    image: nginx\n// after\nservices:\n  web:\n    image: nginx","handlingStrategy":"validation","validationCode":"const doc = yaml.load(editorValue);\nif (!doc || typeof doc.services !== \"object\" || Array.isArray(doc.services)) {\n  showError(\"YAML must contain a top-level 'services' mapping.\");\n  return;\n}","typeGuard":"function hasValidServices(doc) {\n  return doc !== null && typeof doc === \"object\"\n    && typeof doc.services === \"object\" && doc.services !== null\n    && !Array.isArray(doc.services);\n}","tryCatchPattern":"try {\n  const { config, doc } = parseCompose(editorValue);\n  saveStack(config);\n} catch (e) {\n  if (e.message === \"Services must be an object\") {\n    showError(\"Add a top-level services: mapping to your compose YAML.\");\n  } else throw e;\n}","preventionTips":["Keep `services:` as a top-level mapping with correct 2-space indentation","Validate with `docker compose config` or a YAML linter before saving","Use `services: {}` if a stack temporarily has no services","Avoid pasting non-compose YAML into the stack editor"],"tags":["yaml","compose","validation","frontend"],"backgroundTag":"schema-validation-failed","analyzedSha":"f809ae192b571944ad773e9866d3e67064ae8043","analyzedAt":"2026-08-31T19:13:50.919Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}