How to Include One YAML File Inside Another (In Docker Compose, K8s, CI/CD)
YAML has no native file inclusion mechanism.
There is no !include, no import, no require. The YAML specification deliberately avoids defining how files reference each other — that responsibility belongs to the tools consuming the YAML, not the format itself.
For developers maintaining large infrastructure repositories, this creates a real problem. A monolithic Kubernetes manifest for a production service can easily exceed 500 lines. A Docker Compose file with five microservices plus databases plus caches is unmanageable without splitting it.
Every platform that uses YAML eventually builds its own file inclusion mechanism. Some are elegant. Some are hacked together. None are standardized.
This guide covers how every major YAML-consuming tool handles file includes, and how to compose YAML files when your tool does not support it natively.
Why YAML Has No Native Include
The YAML specification is a data serialization language, not a build system. File inclusion is a content management problem, not a serialization problem.
The spec's philosophy: if a tool needs to compose YAML from multiple files, that tool should define its own mechanism rather than adding complexity to the core language.
This sounds reasonable until you are maintaining a 2000-line GitHub Actions workflow and desperately wishing for a simple import: ./shared-steps.yml.
Docker Compose: extends and Multiple Compose Files
Docker Compose offers two inclusion mechanisms.
extends (Legacy but Still Used)
The extends field references a service defined in another file:
# docker-compose.common.yml
services:
app-base:
image: node:20-alpine
restart: unless-stopped
networks:
- app-network
# docker-compose.services.yml
services:
api:
extends:
file: docker-compose.common.yml
service: app-base
environment:
- PORT=3000
worker:
extends:
file: docker-compose.common.yml
service: app-base
command: npm run worker
Docker Compose resolves extends at runtime, pulling the base service's configuration and merging it with the local definition. Local keys override inherited ones.
Caveat: extends is deprecated in newer Docker Compose versions but still widely documented and used. The recommended replacement is multiple compose files.
Multiple Compose Files
docker compose -f docker-compose.base.yml -f docker-compose.prod.yml up
The second file overlays onto the first. Keys in the second file override the first:
# docker-compose.base.yml
services:
api:
image: api:latest
ports:
- "3000:3000"
environment:
- NODE_ENV=development
# docker-compose.prod.yml
services:
api:
environment:
- NODE_ENV=production
- DB_HOST=prod-db.internal
The result is a combined configuration with NODE_ENV=production overriding the base value, and DB_HOST added.
This is the most practical inclusion mechanism for Docker Compose because it requires no special syntax — just shell-level file concatenation.
Kubernetes: Kustomize and Helm
Kubernetes does not support "including" one YAML file inside another. Instead, the ecosystem provides two powerful composition tools.
Kustomize
Kustomize is Kubernetes-native configuration customization. It does not use templates — it patches and overlays base YAML files.
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- path: replica-patch.yaml
Kustomize composes multiple YAML files into a single output by layering patches onto base resources. This is effectively file inclusion with override capabilities.
Helm
Helm uses Go templates with include and template directives:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: {{ .Values.appName }}
env:
{{- include "app.env" . | indent 10 }}
# templates/_helpers.tpl
{{- define "app.env" }}
- name: DB_HOST
value: {{ .Values.db.host }}
- name: DB_PORT
value: {{ .Values.db.port | quote }}
{{- end }}
Helm's include is a template directive, not a YAML feature, but it achieves the same goal — reusable YAML blocks composed from separate definitions.
Ansible: include_tasks and import_playbook
Ansible's YAML-based playbooks have the richest include system in the YAML ecosystem.
# main.yml
- name: Configure web server
hosts: webservers
tasks:
- include_tasks: setup.yml
- include_tasks: deploy.yml
- include_tasks: healthcheck.yml
# setup.yml
- name: Install packages
apt:
name: nginx
state: present
- name: Create config directories
file:
path: /etc/nginx/sites-enabled
state: directory
Ansible also supports dynamic includes with include_vars:
- name: Load environment-specific variables
include_vars: "{{ env }}.yml"
This loads production.yml, staging.yml, or development.yml based on the env variable — true parameterized file inclusion.
GitHub Actions: Reusable Workflows and Composite Actions
GitHub Actions solves file composition through two mechanisms.
Reusable Workflows
# .github/workflows/deploy.yml
jobs:
deploy:
uses: ./.github/workflows/shared-deploy.yml
with:
environment: production
The uses keyword calls another workflow file as a job. The caller passes inputs that the reusable workflow receives as parameters.
Composite Actions
# .github/actions/setup/action.yml
name: "Setup"
description: "Setup Node and install dependencies"
inputs:
node-version:
description: "Node.js version"
required: true
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
shell: bash
Workflows include composite actions as steps:
jobs:
build:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
node-version: 20
- run: npm run build
CI/CD Pipeline Inclusion Patterns
GitLab CI: include
GitLab CI has one of the cleanest inclusion systems:
# .gitlab-ci.yml
include:
- local: 'templates/common.yml'
- project: 'myteam/shared-ci'
file: 'pipelines/deploy.yml'
- remote: 'https://example.com/templates/security.yml'
Includes are resolved before the pipeline runs, merging the included files into the final configuration.
CircleCI: orbs and executors
version: 2.1
orbs:
node: circleci/node@5.0
executors:
default:
docker:
- image: cimg/node:20.0
jobs:
build:
executor: default
steps:
- node/install
CircleCI's orb system is essentially a distributed include mechanism with versioning and namespace isolation.
Preprocessor-Based Includes
When your tool does not support includes natively, use a preprocessor.
yq-based include
yq is a YAML processor that can merge files:
yq eval-all '. as $item ireduce ({}; . *+ $item)' base.yml override.yml
This deep-merges two YAML files, with the second overriding the first.
Custom preprocessor with anchors
Define an include convention using anchors:
# config.yml
defaults: &defaults
app_name: myapp
port: 8080
# Include via preprocessor
#include: partials/database.yml
services:
web:
<<: *defaults
Then run a script that replaces #include: path with the file content before parsing:
python preprocess.py config.yml | yamllint -
M4 macro preprocessor
For extreme cases, some teams use m4 — the Unix macro processor — to include files before YAML parsing:
m4 -I ./partials config.yml.m4 > config.yml
This is overkill for most projects but works when you need file inclusion across many YAML files in a monorepo.
When You Should NOT Include Files
File inclusion is not always the right answer.
Too many small files. Splitting a 50-line config into 10 files of 5 lines each makes configuration harder to understand, not easier.
Cross-tool incompatibility. If you use a Docker Compose extends pattern that only works in Compose, you cannot reuse those files in Kubernetes or GitHub Actions.
Versioning complexity. Reusable YAML files that are included by multiple projects create versioning and dependency management challenges that outweigh the benefits of deduplication.
Debugging difficulty. Included files make it harder to trace configuration issues because the final resolved YAML is not visible in any single file.
The rule of thumb: if your base YAML file exceeds 300 lines and contains clearly separable concerns (databases vs application vs monitoring), splitting with includes is worth it. If your file is under 100 lines, keep it monolithic.
FAQ
Can you include one YAML file in another?
The YAML specification does not define any include or import directive. However, most platforms that consume YAML files provide their own inclusion mechanisms. Docker Compose supports extends and multi-file composition with docker compose -f. Kubernetes uses Kustomize for resource composition or Helm templates for Go-template includes. Ansible has include_tasks and import_playbook. GitLab CI has an include keyword. GitHub Actions supports reusable workflows and composite actions. If your platform lacks native includes, you can use a preprocessing tool like yq to merge YAML files before parsing.
How do I split Docker Compose YAML into multiple files?
Docker Compose supports multiple compose files natively. Define common configuration in a docker-compose.base.yml file, then create environment-specific files that override only the values that differ. Run with docker compose -f docker-compose.base.yml -f docker-compose.prod.yml up. The second file overlays onto the first — service definitions, environment variables, and other settings in the second file merge with or override values from the first. This is the recommended approach over the legacy extends directive.
How do I import a YAML file in Kubernetes?
Kubernetes does not support YAML file import natively. The standard approach is Kustomize: define base resources in one directory and create overlays that reference and patch those bases. Kustomize is built into kubectl (kubectl kustomize) and handles composition, merging, and patching of Kubernetes YAML files. For template-based composition, Helm uses Go template include and template directives to compose reusable YAML fragments from _helpers.tpl files.
Does GitHub Actions support YAML file includes?
Yes, through two mechanisms. Reusable workflows let you reference another .github/workflows/*.yml file as a job using the uses keyword, passing inputs and receiving outputs. Composite actions let you define reusable step sequences in .github/actions/*/action.yml files that workflows reference as single steps. Both mechanisms enable modular workflow composition without duplicating configuration across multiple workflow files.
What is the best way to share YAML configuration across projects?
The best approach depends on your tooling. For Docker Compose, use multi-file composition. For Kubernetes, use Kustomize overlays or Helm charts published to a registry. For CI/CD pipelines, use reusable workflows or includes (GitHub Actions reusable workflows, GitLab CI include, CircleCI orbs). For general-purpose YAML across unrelated tools, consider a preprocessing step using yq or a custom script that concatenates or merges files before the YAML parser reads them. Avoid inventing custom include syntax — use the include mechanism your platform provides, or none at all.
Final Thoughts
YAML's lack of native file inclusion is frustrating, but every major platform has built reasonable solutions on top of it.
The key is to use the inclusion mechanism your platform provides, not to fight against it. Docker Compose multi-file composition, Kubernetes Kustomize, and GitHub Actions reusable workflows all work well within their ecosystems.
If you are stuck with a tool that does not support includes and your YAML files are growing unmanageable, a simple preprocessing step with yq or a shell script is better than living with a 2000-line configuration file.
And when you are debugging an included YAML file that is not resolving as expected, paste the final composed output into a YAML formatter — seeing the resolved configuration makes it immediately clear which file's values took precedence.
For more on indentation issues that commonly arise when composing YAML files, see How to Fix YAML Indentation Errors and Why Your YAML Is Invalid. If you are looking for a quick way to validate composed configs, Best YAML Formatter Tools covers the most reliable validators.