From 5d538851e1ba60883b110f2569368b5291dd6d75 Mon Sep 17 00:00:00 2001 From: defangdevs Date: Mon, 20 Jul 2026 02:53:27 -0700 Subject: [PATCH 1/6] feat: add env-file input for compose interpolation Exposes the CLI's new --env-file support via a COMPOSE_ENV_FILES environment variable, so workflows can point compose interpolation at a non-default env file (e.g. .env.production). Whitespace-delimited input is converted to the comma-separated form the CLI expects, matching how `project` maps to COMPOSE_PROJECT_NAME. - action.yaml: new env-file input; set COMPOSE_ENV_FILES - README: document the env-file input - test.yaml + fixtures: assert env-file interpolation reaches the CLI Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Sw7j9FYnbChZJbYArxbhuo --- .github/workflows/test.yaml | 18 ++++++++++++++++++ README.md | 18 ++++++++++++++++++ action.yaml | 8 ++++++++ test/action.env | 1 + test/compose.envfile.yaml | 7 +++++++ 5 files changed, 52 insertions(+) create mode 100644 test/action.env create mode 100644 test/compose.envfile.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0c97f2e..01a334b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -55,5 +55,23 @@ jobs: verbose: true command: "compose up --dry-run --project-name github-action-test" + - name: Deploy with env-file + id: env-file-test + uses: ./ + continue-on-error: true # Ignore dry run error + with: + cwd: "./test" + compose-files: "compose.envfile.yaml" + env-file: "action.env" + command: "compose config --project-name github-action-test" + + - name: Verify env-file interpolation + shell: bash + run: | + echo "${STDOUT}" | grep -q "hello-from-env-file" \ + || { echo "expected env-file value not found in output"; exit 1; } + env: + STDOUT: ${{ steps.env-file-test.outputs.stdout }} + - name: Teardown run: defang config rm --project-name github-action-test DEFANG_GH_ACTION_TEST_MESSAGE diff --git a/README.md b/README.md index 318d3cd..f8c83c9 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,24 @@ jobs: DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }} ``` +### Using an Environment File + +By default, Defang loads a `.env` file next to your Compose file to resolve `${VARIABLE}` interpolation. To use a different environment file (or several), set the `env-file` input. This mirrors `docker compose --env-file` and, when set, replaces the default `.env`. + +```yaml +jobs: + test: + # [...] + steps: + # [...] + - name: Deploy + uses: DefangLabs/defang-github-action@v2 + with: + env-file: ".env.production" +``` + +Pass multiple files whitespace-delimited (`env-file: ".env .env.production"`); later files override earlier ones. + ### Projects in a Subdirectory If your Compose file is in a different directory than your project root, you can specify the path to the project in the `cwd` input. diff --git a/action.yaml b/action.yaml index 3473a37..ff752e5 100644 --- a/action.yaml +++ b/action.yaml @@ -30,6 +30,10 @@ inputs: description: "The compose files to deploy. Format 'file1 file2 file3'" required: false default: "" + env-file: + description: "Environment file(s) used for compose interpolation instead of the default .env. Format 'file1 file2 file3'" + required: false + default: "" cwd: description: "The directory containing the compose file to deploy." required: false @@ -111,6 +115,10 @@ runs: [ -n "${{ inputs['provider'] }}" ] && echo "DEFANG_PROVIDER=${{ inputs['provider'] }}" >> $GITHUB_ENV || true [ -n "${{ inputs['stack'] }}" ] && echo "DEFANG_STACK=${{ inputs['stack'] }}" >> $GITHUB_ENV || true [ -n "${{ inputs['project'] }}" ] && echo "COMPOSE_PROJECT_NAME=${{ inputs['project'] }}" >> $GITHUB_ENV || true + # The CLI reads COMPOSE_ENV_FILES (comma-separated) like docker compose; convert the space-separated input. + [ -n "$ENV_FILE" ] && echo "COMPOSE_ENV_FILES=$(echo "$ENV_FILE" | tr ' ' ',')" >> $GITHUB_ENV || true + env: + ENV_FILE: ${{ inputs['env-file'] }} - name: Login to Defang shell: bash diff --git a/test/action.env b/test/action.env new file mode 100644 index 0000000..7fde724 --- /dev/null +++ b/test/action.env @@ -0,0 +1 @@ +GREETING=hello-from-env-file diff --git a/test/compose.envfile.yaml b/test/compose.envfile.yaml new file mode 100644 index 0000000..d13e8f0 --- /dev/null +++ b/test/compose.envfile.yaml @@ -0,0 +1,7 @@ +services: + web: + build: . + ports: + - "3000:3000" + environment: + GREETING: ${GREETING} From 3318d75c947410fa7abdbe0de83945719c445b31 Mon Sep 17 00:00:00 2001 From: defangdevs Date: Mon, 24 Aug 2026 14:38:06 -0700 Subject: [PATCH 2/6] fix: capture stdout in env-file test so verification step can see output The Verify step greps steps.env-file-test.outputs.stdout, but capture-output defaults to false, so the output was always empty and the check always failed independent of whether interpolation worked. --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fd602bc..08ac24f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -56,6 +56,7 @@ jobs: compose-files: "compose.envfile.yaml" env-file: "action.env" command: "compose config --project-name github-action-test" + capture-output: true - name: Verify env-file interpolation shell: bash From d8ec594238670c861dba3a19194905ef6a44d5cc Mon Sep 17 00:00:00 2001 From: defangdevs Date: Mon, 24 Aug 2026 14:49:57 -0700 Subject: [PATCH 3/6] fix: address CodeRabbit review feedback on env-file input - enable pipefail on the two new Bash pipelines so a failing tr/grep isn't masked - normalize ENV_FILE (trim, collapse whitespace, support multiline YAML lists) instead of only substituting spaces - always write COMPOSE_ENV_FILES, even empty, so a prior step's env-file doesn't leak into a later step in the same job - document the CLI v3.12.0+ requirement for env-file in the README --- .github/workflows/test.yaml | 1 + README.md | 2 ++ action.yaml | 9 +++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 08ac24f..fd2836d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -61,6 +61,7 @@ jobs: - name: Verify env-file interpolation shell: bash run: | + set -o pipefail echo "${STDOUT}" | grep -q "hello-from-env-file" \ || { echo "expected env-file value not found in output"; exit 1; } env: diff --git a/README.md b/README.md index 662ce86..448ec94 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ jobs: By default, Defang loads a `.env` file next to your Compose file to resolve `${VARIABLE}` interpolation. To use a different environment file (or several), set the `env-file` input. This mirrors `docker compose --env-file` and, when set, replaces the default `.env`. +Requires Defang CLI `v3.12.0` or newer. Older CLI versions ignore this input and silently fall back to the default `.env` file. + ```yaml jobs: test: diff --git a/action.yaml b/action.yaml index b2975b2..4f44eed 100644 --- a/action.yaml +++ b/action.yaml @@ -118,12 +118,17 @@ runs: - name: Set Defang environment variables shell: bash run: | + set -o pipefail + [ -n "$RUNNER_DEBUG" ] && echo "DEFANG_DEBUG=$RUNNER_DEBUG" >> $GITHUB_ENV || true [ -n "${{ inputs['provider'] }}" ] && echo "DEFANG_PROVIDER=${{ inputs['provider'] }}" >> $GITHUB_ENV || true [ -n "${{ inputs['stack'] }}" ] && echo "DEFANG_STACK=${{ inputs['stack'] }}" >> $GITHUB_ENV || true [ -n "${{ inputs['project'] }}" ] && echo "COMPOSE_PROJECT_NAME=${{ inputs['project'] }}" >> $GITHUB_ENV || true - # The CLI reads COMPOSE_ENV_FILES (comma-separated) like docker compose; convert the space-separated input. - [ -n "$ENV_FILE" ] && echo "COMPOSE_ENV_FILES=$(echo "$ENV_FILE" | tr ' ' ',')" >> $GITHUB_ENV || true + # The CLI reads COMPOSE_ENV_FILES (comma-separated) like docker compose; convert the + # whitespace-separated input (spaces or newlines, so multiline YAML lists work too). + # Always write it, even empty, so a prior step's env-file doesn't leak into a later + # step of the same job that doesn't set one (GITHUB_ENV entries persist job-wide). + echo "COMPOSE_ENV_FILES=$(echo $ENV_FILE | xargs | tr ' ' ',')" >> $GITHUB_ENV # Docker Compose (and the Defang CLI) natively read the compose file # list from COMPOSE_FILE, so export that once instead of building a # repeated -f/-f/-f list in every step. The unquoted echo collapses From e8158dc1d438e6cfcb5c2df815b45ece8f9849cd Mon Sep 17 00:00:00 2001 From: defangdevs Date: Mon, 24 Aug 2026 14:57:04 -0700 Subject: [PATCH 4/6] fix: guard env-file normalization and check its exit status Only run the xargs/tr pipeline when env-file is set, and abort before writing to GITHUB_ENV if normalization fails, instead of silently continuing on error. --- action.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/action.yaml b/action.yaml index 4f44eed..54a2caf 100644 --- a/action.yaml +++ b/action.yaml @@ -128,7 +128,15 @@ runs: # whitespace-separated input (spaces or newlines, so multiline YAML lists work too). # Always write it, even empty, so a prior step's env-file doesn't leak into a later # step of the same job that doesn't set one (GITHUB_ENV entries persist job-wide). - echo "COMPOSE_ENV_FILES=$(echo $ENV_FILE | xargs | tr ' ' ',')" >> $GITHUB_ENV + if [ -n "$ENV_FILE" ]; then + if ! normalized_env_files="$(echo $ENV_FILE | xargs | tr ' ' ',')"; then + echo "::error::Failed to normalize env-file input" >&2 + exit 1 + fi + echo "COMPOSE_ENV_FILES=$normalized_env_files" >> $GITHUB_ENV + else + echo "COMPOSE_ENV_FILES=" >> $GITHUB_ENV + fi # Docker Compose (and the Defang CLI) natively read the compose file # list from COMPOSE_FILE, so export that once instead of building a # repeated -f/-f/-f list in every step. The unquoted echo collapses From 1aeb4693bba10a514603d3abc18cc7d5658e071f Mon Sep 17 00:00:00 2001 From: defangdevs Date: Mon, 24 Aug 2026 15:01:46 -0700 Subject: [PATCH 5/6] fix: avoid unquoted echo expansion when normalizing env-file input Unquoted glob-expands the value and can mangle filenames starting with '-' or containing wildcards. Use 'read -a' to split on whitespace without shell expansion instead. --- action.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/action.yaml b/action.yaml index 54a2caf..5867ec8 100644 --- a/action.yaml +++ b/action.yaml @@ -129,11 +129,10 @@ runs: # Always write it, even empty, so a prior step's env-file doesn't leak into a later # step of the same job that doesn't set one (GITHUB_ENV entries persist job-wide). if [ -n "$ENV_FILE" ]; then - if ! normalized_env_files="$(echo $ENV_FILE | xargs | tr ' ' ',')"; then - echo "::error::Failed to normalize env-file input" >&2 - exit 1 - fi - echo "COMPOSE_ENV_FILES=$normalized_env_files" >> $GITHUB_ENV + # `read -a` splits on whitespace without invoking shell globbing/expansion, + # so literal filenames (leading '-', wildcard characters) survive intact. + read -r -a env_file_parts <<< "$ENV_FILE" + echo "COMPOSE_ENV_FILES=$(IFS=,; echo "${env_file_parts[*]}")" >> $GITHUB_ENV else echo "COMPOSE_ENV_FILES=" >> $GITHUB_ENV fi From 12eefc934022200c64321c6dd33fbc0a64c0e32f Mon Sep 17 00:00:00 2001 From: defangdevs Date: Mon, 24 Aug 2026 15:05:20 -0700 Subject: [PATCH 6/6] fix: restore multiline env-file support broken by the previous fix 'read -a <<< "$ENV_FILE"' only consumes the here-string's first line, so a multiline (YAML block-scalar) env-file input silently dropped every line after the first. Use 'set -f' + unquoted array expansion instead: it word-splits on all of IFS (space/tab/newline) across the whole string while still skipping glob expansion, so wildcard/hyphen-prefixed filenames stay literal. Verified against simple, multiline, leading-'-', wildcard, multi-space, and empty inputs. --- action.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/action.yaml b/action.yaml index 5867ec8..e702502 100644 --- a/action.yaml +++ b/action.yaml @@ -129,9 +129,12 @@ runs: # Always write it, even empty, so a prior step's env-file doesn't leak into a later # step of the same job that doesn't set one (GITHUB_ENV entries persist job-wide). if [ -n "$ENV_FILE" ]; then - # `read -a` splits on whitespace without invoking shell globbing/expansion, - # so literal filenames (leading '-', wildcard characters) survive intact. - read -r -a env_file_parts <<< "$ENV_FILE" + # Disable globbing so the unquoted expansion below only word-splits on + # IFS (space/tab/newline, so multiline YAML lists work) without + # expanding literal filenames that start with '-' or contain wildcards. + set -f + env_file_parts=($ENV_FILE) + set +f echo "COMPOSE_ENV_FILES=$(IFS=,; echo "${env_file_parts[*]}")" >> $GITHUB_ENV else echo "COMPOSE_ENV_FILES=" >> $GITHUB_ENV