Build a Dagger JavaScript Quality Gate
Run JavaScript tests and linting locally and in GitHub Actions with Dagger.
Introduction
30 Second Summary
A code change can behave perfectly on your laptop and fail as soon as a shared runner uses a different toolchain. That gap turns every push into a question about whether the code or the environment caused the failure.
In this project, you will build a portable JavaScript quality gate with Dagger. You will watch it catch a deliberate defect locally before the repaired test and lint checks pass through GitHub Actions.
What You'll Build
Your demo shows one dagger check trace catch a real defect before the same quality gate finishes green on your Linux device and a GitHub-hosted runner.
By the end of this project, you'll have:
- A deployment verdict function that reports READY only when both quality signals are healthy. Every other combination reports BLOCKED.
- A Vitest suite that catches a deliberate defect. You can repair the application logic and watch all three test cases pass.
- A portable quality gate that runs Vitest plus ESLint inside containers from one command. Repeated local runs reuse unchanged setup work. GitHub Actions produces the same green result.
- Secret Mission: Add Prettier as a third check that fails on untidy code before a Dagger-generated changeset formats it.
Are there any prerequisites?
You need basic container familiarity on a Linux device with an installed runtime. Dagger runs Node.js and npm inside containers, so a local Node.js installation is unnecessary.
Before We Start
Before hands-on work begins, this step defines the purpose of your local-first Dagger quality gate. It protects deployment-readiness behavior consistently across local runs, pushes, and GitHub Actions.
Set Up Dagger on Linux
A portable quality gate can only be trusted when its local tools are reachable. Dagger needs a working container runtime before it can execute isolated checks.
This setup verifies Git for version control. It also pins Dagger to v1.0.0-beta.11 so later local runs match the GitHub Actions workflow.
In this step, get ready to:
- Confirm that Docker Engine or Podman responds on Linux.
- Make Git available for the project.
- Install the pinned Dagger CLI in a new local repository.
Verify the container runtime and Git
The container runtime gives Dagger the engine it needs to create isolated environments. Your computer may use Docker Engine or Podman.
- Choose the tab for the container runtime already installed on your computer.
Docker Engine
- Check that Docker Engine responds by running this command:
docker info
What does this command check?
This command asks Docker Engine for information about its local runtime. A completed response confirms that the runtime is reachable.
You'll see information about the Docker installation. This proves the runtime can answer local requests.
Docker Engine not responding?
Confirm that Docker Engine is running before repeating the check. A stopped service cannot return runtime information.
Check whether your Linux user has permission to access Docker Engine.
Ask for help with your runtime setup: Help me diagnose why Docker Engine does not respond on Linux.
Podman
- Check that Podman responds by running this command:
podman info
What does this command check?
This command asks Podman for information about its local runtime. A completed response confirms that the runtime is reachable.
You'll see information about the Podman installation. This proves the runtime can answer local requests.
Podman not responding?
Confirm that Podman is running correctly before repeating the check. Review any permission details reported by your terminal.
Ask for help with your runtime setup: Help me diagnose why Podman does not respond on Linux.
Git records the workspace files that you later push to GitHub. The first check tells you whether Git is already available.
- Check for Git by running this command:
git --version
What does this command check?
This command asks Git to print its installed version. A version number confirms that your shell can find the Git executable.
✔️ I see a version number
Good progress. Git is available for the repository you create later in this step.
ⓧ Git is not installed
Git installation uses your Linux distribution's package manager. These commands require administrator privileges.
Use only the command that matches your Linux distribution.
- Install Git on Debian or Ubuntu by running:
apt-get install git
What does this command do?
This asks the Debian or Ubuntu package manager to install Git from the configured software repositories.
- Install Git on Fedora by running:
dnf install git
What does this command do?
This asks the Fedora package manager to install Git from the configured software repositories.
- Install Git on Arch Linux by running:
pacman -S git
What does this command do?
This asks the Arch Linux package manager to install Git from the configured software repositories.
- Install Git on openSUSE by running:
zypper install git
What does this command do?
This asks the openSUSE package manager to install Git from the configured software repositories.
- Install Git on Alpine Linux by running:
apk add git
What does this command do?
This asks the Alpine Linux package manager to add Git from the configured software repositories.
Git installation failing?
Confirm that the command matches your Linux distribution. Make sure the current shell has administrator privileges.
Check that your package manager can reach its configured software repositories.
Ask for help with the installation: Help me install Git on my Linux distribution.
- Confirm that Git is now available by running:
git --version
What confirms success?
Git prints its installed version when the executable is available in your shell. This gives the new repository a working version-control tool.
You'll see a Git version number in the terminal. Git is ready for the project.
Git still unavailable?
Close the current terminal after the installation completes. Start a fresh terminal session before repeating the version check.
Ask for help with the remaining issue: Help me find why Git is still unavailable after installation.
Install the pinned Dagger CLI
The workspace model used in this project targets the Dagger v1.0.0-beta.11 release. Pinning the CLI prevents later commands from drifting to a different workflow.
Dagger runs Node.js tooling inside containers. You do not need to install Node.js on your Linux computer.
- Check whether a Dagger CLI is already available by running:
dagger version
What does this command check?
This command prints the active Dagger CLI version. The result tells you whether the required beta is already available.
✔️ I see the pinned beta
Your active Dagger version matches this project. Continue with the user-local installation below to place the pinned binary in the required location.
ⓧ I see another version
This project depends on the pinned beta workflow. The installer below places the required release in your user-local binary directory.
ⓧ Dagger is not installed
Your shell cannot find a Dagger executable yet. The installer below adds the pinned release without changing system-wide directories.
- Install Dagger v1.0.0-beta.11 in your user-local binary directory by running:
curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=1.0.0-beta.11 BIN_DIR=$HOME/.local/bin sh
What does this command do?
This downloads Dagger's official Linux installation script. The version setting pins the beta used throughout the project.
The binary directory setting places Dagger under $HOME/.local/bin. This keeps the installation scoped to your Linux user.
- Make the user-local binary directory available in the current shell by running:
export PATH="$HOME/.local/bin:$PATH"
Why update the current shell?
The path update lets this terminal find the binary under $HOME/.local/bin. It applies immediately to the current shell session.
- Verify the installed Dagger release by running:
dagger version
What confirms the installation?
The command reads the Dagger binary now available through your shell path. Its output identifies the active release.
You should see v1.0.0-beta.11. Your Dagger CLI now matches the workspace model used in the remaining steps.
Dagger version still unavailable?
Repeat the path update in the same terminal that runs the version check. Confirm that the installer placed the binary under $HOME/.local/bin.
A different version usually means another Dagger binary appears earlier in your shell path.
Ask for help with your installation: Help me make the pinned Dagger binary active on Linux.
Create the local Git repository
Every Dagger command in this project runs from the repository root. Creating that location now gives the workspace one consistent home.
- Move to your Desktop by running this command:
cd ~/Desktop
What does this command do?
This changes the terminal's current location to the Desktop directory under your Linux home folder. The project directory you create next stays easy to find.
- Create the dagger-quality-gate directory with a Git repository by running:
mkdir -p dagger-quality-gate && cd dagger-quality-gate && git init -b main
What does this command do?
- The first part creates the dagger-quality-gate directory.
- The second part moves your terminal into that directory.
- The final part initializes a Git repository on the main branch.
That's the foundation in place. Your terminal is now at the repository root that every later Dagger command uses.
Repository initialization failing?
Confirm that your Linux user can create folders on the Desktop. An older Git installation may not support choosing the initial branch during repository creation.
Ask for help with the repository setup: Help me initialize the dagger-quality-gate repository on main.
Before the final checks, do you expect the pinned Dagger CLI and your chosen container runtime to respond from inside the repository?
- Confirm the active Dagger version from the repository root by running:
dagger version
What does this final check prove?
This confirms that the pinned Dagger binary remains available after entering the repository. Later steps can invoke the workspace from this location.
You'll see v1.0.0-beta.11 in the terminal.
- Choose your runtime tab to repeat its final availability check.
Docker Engine
- Confirm Docker Engine still responds by running:
docker info
What confirms readiness?
A completed runtime-information response confirms that Docker Engine remains reachable from the repository root. Dagger now has a supported local runtime available.
Podman
- Confirm Podman still responds by running:
podman info
What confirms readiness?
A completed runtime-information response confirms that Podman remains reachable from the repository root. Dagger now has a supported local runtime available.
Your Linux environment is ready for containerized checks. Next, you'll create a real JavaScript defect and watch Dagger catch it.
Expose the Failing Vitest Check
Your Dagger CLI is ready in the dagger-quality-gate repository. The verified container runtime can now execute isolated checks.
A quality gate earns trust when it catches a deterministic defect. You will build a temporary verdict plus a test suite, then let Vitest judge them inside Dagger.
In this step, get ready to:
- Create the JavaScript project configuration.
- Add the temporary verdict function plus its test suite.
- Install the Vitest module and expose the intentional failure.
Create the project configuration
Vitest discovers this project through a configuration file. A JSON manifest pins the test dependency.
- Use your Linux application search to find Visual Studio Code.
- Select Visual Studio Code from the results.
You will see the Visual Studio Code window ready for a folder.
- Choose the folder-opening option from the top menu.
- Select the existing dagger-quality-gate folder.
You will see the repository contents in the file sidebar.
- Create package.json at the top level of dagger-quality-gate with the file sidebar.
You will see package.json open in a new editor tab.
- Define the project manifest by pasting this code:
{
"name": "dagger-quality-gate",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run"
},
"devDependencies": {
"vitest": "5.0.0"
}
}
What does this configuration do?
- The type setting enables ES modules for the JavaScript files.
- The test script gives the module one repeatable test entry point.
- The devDependencies section pins Vitest to 5.0.0.
- Save package.json.
- Confirm the file sidebar lists package.json at the top level of the repository.
Package File Not Saving?
- Check that every opening brace has a matching closing brace.
- Confirm the filename is exactly package.json.
Ask for help checking the manifest syntax: Help me find the syntax problem in my package.json file.
- Create .gitignore at the top level of dagger-quality-gate with the file sidebar.
You will see .gitignore beside package.json.
- Add the generated directories to ignore by pasting this code:
node_modules/
.vitest/
Why Ignore These Directories?
The dependency directory and temporary Vitest data are generated during test runs. Keeping them out of Git leaves the repository focused on project-owned files.
- Save .gitignore.
- Confirm the file contains two ignored directory patterns.
- Create vitest.config.js at the top level of dagger-quality-gate with the file sidebar.
You will see vitest.config.js open beside the other project files.
- Define the test discovery pattern by pasting this code:
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["test/**/*.test.js"],
},
});
How Does Test Discovery Work?
- The defineConfig() helper defines the Vitest configuration.
- The include pattern limits discovery to JavaScript test files inside test/.
- Save vitest.config.js.
- Confirm the file sidebar lists all three configuration files.
Test Configuration Look Wrong?
- Confirm vitest.config.js sits at the top level of dagger-quality-gate.
- Check that the import path is exactly vitest/config.
Ask for help comparing the configuration: Help me check my Vitest configuration file.
Add the temporary verdict and tests
The verdict function is the behavior under protection. Its temporary body creates the defect that the test suite can expose.
- Create a folder named src inside dagger-quality-gate with the file sidebar.
You will see the src folder at the top level of the repository.
- Create verdict.js inside the src folder.
You will see src/verdict.js open in the editor.
- Add the temporary verdict implementation by pasting this code:
export function deploymentVerdict({ testsPassed, lintPassed }) {
return "BLOCKED";
}
What Does the Temporary Verdict Do?
- The exported deploymentVerdict() function accepts the test signal through testsPassed.
- The function accepts the lint signal through lintPassed.
- The temporary body returns BLOCKED for every input.
- Save src/verdict.js.
- Confirm the file sidebar shows verdict.js inside src.
Verdict File Not Recognized?
- Confirm the file path is exactly src/verdict.js.
- Check the spelling of deploymentVerdict against the code block.
Ask for help checking the function: Help me compare my temporary deploymentVerdict function.
- Create a folder named test inside dagger-quality-gate with the file sidebar.
You will see the test folder beside src.
- Create verdict.test.js inside the test folder.
You will see test/verdict.test.js open in the editor.
- Define the expected verdict behavior by pasting this code:
import { describe, expect, test } from "vitest";
import { deploymentVerdict } from "../src/verdict.js";
describe("deploymentVerdict", () => {
test("returns READY when every signal passes", () => {
expect(
deploymentVerdict({ testsPassed: true, lintPassed: true }),
).toBe("READY");
});
test("returns BLOCKED when tests fail", () => {
expect(
deploymentVerdict({ testsPassed: false, lintPassed: true }),
).toBe("BLOCKED");
});
test("returns BLOCKED when lint fails", () => {
expect(
deploymentVerdict({ testsPassed: true, lintPassed: false }),
).toBe("BLOCKED");
});
});
What Does This Test Suite Protect?
- The imports provide the Vitest test helpers plus deploymentVerdict().
- The first case expects READY when both quality signals pass.
- The second case expects BLOCKED when tests fail.
- The third case expects BLOCKED when linting fails.
- Save test/verdict.test.js.
- Confirm the editor shows three test() cases inside describe().
Test File Showing a Problem?
- Confirm the function import points to ../src/verdict.js.
- Check that every expect() call closes before its toBe() assertion.
Ask for help checking the suite: Help me find the mismatch in my verdict tests.
Install the module and run the failing gate
A Dagger workspace connects reusable modules to checks owned by your repository.
The pinned Vitest module installs dependencies inside its container. It also executes the test suite there.
- Switch back to the terminal from the previous step.
- Install the pinned Vitest module into the Dagger workspace by running this command:
dagger install github.com/dagger/vitest@736010a059e07bd261d6097f8471e5c83a2b84aa
What Does This Command Do?
- The module source is pinned to 736010a059e07bd261d6097f8471e5c83a2b84aa for repeatable behavior.
- Dagger creates dagger.toml at the repository root.
- The installed module contributes the containerized Vitest check to the workspace.
- Return to Visual Studio Code.
- Confirm dagger.toml appears at the top level of dagger-quality-gate.
The generated workspace configuration now points to the pinned Vitest module.
Module Installation Failed?
- Confirm the terminal prompt is still inside the dagger-quality-gate repository.
- Confirm the container runtime from the previous step still responds.
- Check that dagger version still reports v1.0.0-beta.11.
Ask for help with the installation: Help me troubleshoot the pinned Dagger Vitest module installation.
✔️ Awesome, I've got everything!
Great. Your project files are saved, and dagger.toml points to the pinned Vitest module.
ⓧ I'd like to double check the full code
Compare each project file with these complete versions.
node_modules/
.vitest/
What Belongs Outside Git?
This file excludes installed dependencies plus temporary Vitest data.
[modules.vitest]
source = "github.com/dagger/vitest@736010a059e07bd261d6097f8471e5c83a2b84aa"
What Module Is Installed?
This workspace entry pins the reusable Vitest module to one exact Git commit.
{
"name": "dagger-quality-gate",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run"
},
"devDependencies": {
"vitest": "5.0.0"
}
}
What Does the Manifest Define?
This manifest defines an ES module project with a test script and the pinned Vitest dependency.
export function deploymentVerdict({ testsPassed, lintPassed }) {
return "BLOCKED";
}
Where Is the Defect?
The temporary function returns BLOCKED for every combination of quality signals.
import { describe, expect, test } from "vitest";
import { deploymentVerdict } from "../src/verdict.js";
describe("deploymentVerdict", () => {
test("returns READY when every signal passes", () => {
expect(
deploymentVerdict({ testsPassed: true, lintPassed: true }),
).toBe("READY");
});
test("returns BLOCKED when tests fail", () => {
expect(
deploymentVerdict({ testsPassed: false, lintPassed: true }),
).toBe("BLOCKED");
});
test("returns BLOCKED when lint fails", () => {
expect(
deploymentVerdict({ testsPassed: true, lintPassed: false }),
).toBe("BLOCKED");
});
});
What Outcomes Are Protected?
The suite requires a healthy pair of signals to produce READY. It requires either unhealthy signal to produce BLOCKED.
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["test/**/*.test.js"],
},
});
Which Tests Are Discovered?
This configuration discovers JavaScript test files below the test directory.
- Switch back to the same terminal in the dagger-quality-gate repository root.
Before you run the gate, do you think the healthy-input case can pass with the current return value?
- Execute the containerized quality gate by running this command:
dagger check
This Failure Is Intentional
Your trace reaches the Vitest check. The healthy-input case expects READY.
The deploymentVerdict() function returns BLOCKED. The Dagger check exits non-zero.
That failure is useful. Your gate has stopped the wrong deployment verdict before it could reach CI.
Did the Check Stop Before Vitest?
- Confirm dagger.toml contains the pinned Vitest module source.
- Confirm vitest.config.js exists at the repository root.
- Confirm the verified container runtime is still available.
Ask for help tracing the failure: Help me determine why dagger check did not reach the Vitest assertion.
Your Dagger check now fails for the right reason. Next, you will repair only the verdict logic. The same gate will then prove the repair.
Fix the Gate and Inspect Caching
Your earlier run proved that the Dagger quality gate catches a real defect. The Vitest check stopped the incorrect deployment verdict.
This step repairs only the application logic that produced the bad result. A passing run will show that the test is a useful guard.
A second unchanged run will reveal which container setup operations Dagger reuses.
In this step, get ready to:
- Replace the temporary verdict with the final conditional logic.
- Run the Dagger check to prove all three tests pass.
- Repeat the unchanged check to inspect reused setup work.
Repair the deployment verdict
The deploymentVerdict() function receives separate signals for tests and linting. A healthy deployment requires both signals.
- Return to Visual Studio Code from the previous step.
- Select src/verdict.js in the Explorer sidebar.
- Find the line containing return "BLOCKED".
- Replace the complete function with the code below:
export function deploymentVerdict({ testsPassed, lintPassed }) {
return testsPassed && lintPassed ? "READY" : "BLOCKED";
}
How the Repaired Verdict Works
- The testsPassed && lintPassed expression becomes true only when both signals are true.
- The conditional expression returns READY for that healthy state.
- The conditional expression returns BLOCKED for every other combination.
- Save src/verdict.js.
Before you run the gate, do you expect the healthy-input test to fail again?
- Switch back to the terminal from earlier.
- Test the repaired verdict by running this command:
dagger check
What Does This Check Prove?
Dagger runs the installed Vitest check against the updated file. You will see all three test cases pass.
Are the Tests Still Failing?
- Confirm that src/verdict.js uses testsPassed && lintPassed as its condition.
- Check that both result strings use uppercase letters.
- Confirm that your terminal is still at the dagger-quality-gate repository root.
Ask for help with the failed assertion: Help me compare my deployment verdict with the three Vitest expectations.
✔️ Awesome, I've got everything!
That was the key repair. Your gate now accepts the healthy case while preserving both blocked cases.
ⓧ I'd like to double check the full code
Compare your complete src/verdict.js file with this version:
export function deploymentVerdict({ testsPassed, lintPassed }) {
return testsPassed && lintPassed ? "READY" : "BLOCKED";
}
What Should Match?
The function keeps the same parameters from the failing version. Only its return logic changes.
Repeat the gate and inspect caching
A cache stores reusable outputs from unchanged work. Dagger can reuse dependency installation and other setup operations.
The pinned Vitest module still executes the tests on each run. This keeps the gate checking the current behavior.
Before you run the unchanged gate again, which parts do you expect Dagger to reuse?
- Run the same quality gate again without changing any project files:
dagger check
What Does the Second Trace Show?
The unchanged workspace passes again. Its trace shows reused dependency installation and setup operations.
Vitest still executes its test suite. Dagger saves setup time while preserving the behavioral check.
- Compare the second terminal trace with the first passing trace.
- Identify the reused dependency installation and unchanged setup operations.
- Confirm that the Vitest test execution appears again.
You have now proved that the local gate stays active while Dagger reuses unchanged setup work.
Do You See No Reused Setup Work?
Changed project inputs can cause Dagger to perform setup work again.
- Run two consecutive checks without changing any project files.
- Confirm that both checks start from the dagger-quality-gate repository root.
- Compare the complete traces for dependency installation and setup operations.
Ask for help reading the trace: Help me identify reused work in my second Dagger check trace.
Your repaired gate now passes while reusing unchanged setup work. Next, you will add ESLint as a second check.
Add ESLint to the Quality Gate
Your Dagger gate already uses Vitest to protect the deployment verdict. The passing tests prove that its behavior matches your expectations.
Tests do not catch every risky code pattern. ESLint adds a separate quality signal without requiring another local toolchain.
In this step, get ready to:
- Add the ESLint script and pin ESLint 10.10.0.
- Create an ESLint configuration that checks the project JavaScript files.
- Run the Vitest and ESLint checks through one Dagger gate.
Add the lint configuration
The project needs to declare its own ESLint version. A lint script gives the ESLint module a consistent way to inspect the repository.
- Switch back to package.json in Visual Studio Code.
- Replace its contents with this version:
{
"name": "dagger-quality-gate",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run",
"lint": "eslint ."
},
"devDependencies": {
"eslint": "10.10.0",
"vitest": "5.0.0"
}
}
What Changed in package.json?
- The lint script gives the project a standard lint command.
- The devDependencies section pins ESLint to 10.10.0.
- The existing Vitest dependency remains pinned to 5.0.0.
- Save package.json.
- Confirm that lint now appears inside scripts.
- Confirm that eslint now appears inside devDependencies.
Seeing a JSON Warning?
Check the comma after the test script. Also confirm that every key and string value uses double quotes.
Help me compare my package.json with the required ESLint version.
The ESLint configuration decides which files belong to the check. It also turns the prefer-const rule into a gate failure.
- Create eslint.config.js beside package.json using the new-file control in the Visual Studio Code file sidebar.
- Paste this configuration into eslint.config.js:
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["src/**/*.js", "test/**/*.js", "*.config.js"],
rules: {
"prefer-const": "error",
},
},
]);
What Does This Configuration Do?
- The files patterns include source files inside src.
- The test pattern includes JavaScript files inside test.
- The final pattern includes JavaScript configuration files beside package.json.
- The error severity makes a prefer-const violation fail the lint check.
- Save eslint.config.js.
- Confirm that eslint.config.js appears beside package.json in the file sidebar.
Configuration File Missing?
Check that the filename is exactly eslint.config.js. Confirm that it sits beside package.json inside dagger-quality-gate.
Help me place and verify my ESLint configuration file.
Install the ESLint module
A Dagger module exposes ESLint as another workspace check. The module runs the project-owned ESLint version inside a container.
- Switch back to the terminal from earlier.
- Install the pinned ESLint module by running:
dagger install github.com/dagger/eslint@35a0cc0923fdf625608afb1c2b276e00262c0f81
What Does This Command Change?
Dagger adds the pinned ESLint module to dagger.toml. It also generates or updates dagger.lock with resolved module details.
The OCI digest entries inside dagger.lock can vary by CPU architecture. Keep the generated file without hand-editing it.
- Confirm that dagger.toml now contains an ESLint module entry.
- Confirm that dagger.lock appears in the repository.
Good progress. Your workspace now owns two independently versioned checks.
Module Installation Failed?
Confirm that the terminal is still inside dagger-quality-gate. Also confirm that the verified container runtime is still responding.
Help me troubleshoot the pinned Dagger ESLint module installation.
Run both quality checks
The workspace can now reveal its complete gate before executing it. Listing the checks confirms that Dagger discovered both installed modules.
- List the available workspace checks by running:
dagger check -l
What Should the List Show?
You should see entries for the Vitest check and the ESLint check. This proves both modules are part of the same workspace gate.
Before you run the complete gate, do you expect the new lint check to agree with the passing tests?
- Execute every installed check by running:
dagger check
What Does the Combined Run Prove?
Dagger runs the Vitest check and the ESLint check through the same workspace command. Each check uses its project-owned package version inside a container.
You should see both checks finish successfully. That gives your local gate separate protection for behavior and code quality.
One of the Checks Failed?
If the test check fails, compare src/verdict.js with the working version below. If the lint check fails, compare eslint.config.js and package.json with the references below.
Help me identify which Dagger quality check failed.
✔️ Awesome, I've got everything!
Your gate now lists Vitest and ESLint. Both checks pass from the repository root.
Keep the generated dagger.lock file for the upcoming commit. Its resolved OCI digest entries can differ across CPU architectures.
ⓧ I'd like to double check the full code
Compare each project file with the cumulative versions below. Keep your generated dagger.lock file without replacing its architecture-specific entries.
Check .gitignore
node_modules/
.vitest/
What to Compare
This file excludes installed packages and the Vitest cache from Git tracking.
Check package.json
{
"name": "dagger-quality-gate",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run",
"lint": "eslint ."
},
"devDependencies": {
"eslint": "10.10.0",
"vitest": "5.0.0"
}
}
What to Compare
This file contains both scripts. It also pins the two project-owned development dependencies.
Check src/verdict.js
export function deploymentVerdict({ testsPassed, lintPassed }) {
return testsPassed && lintPassed ? "READY" : "BLOCKED";
}
What to Compare
The verdict returns READY only when both quality signals pass.
Check test/verdict.test.js
import { describe, expect, test } from "vitest";
import { deploymentVerdict } from "../src/verdict.js";
describe("deploymentVerdict", () => {
test("returns READY when every signal passes", () => {
expect(
deploymentVerdict({ testsPassed: true, lintPassed: true }),
).toBe("READY");
});
test("returns BLOCKED when tests fail", () => {
expect(
deploymentVerdict({ testsPassed: false, lintPassed: true }),
).toBe("BLOCKED");
});
test("returns BLOCKED when lint fails", () => {
expect(
deploymentVerdict({ testsPassed: true, lintPassed: false }),
).toBe("BLOCKED");
});
});
What to Compare
The suite covers the healthy result. It also covers each blocked result.
Check vitest.config.js
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["test/**/*.test.js"],
},
});
What to Compare
This configuration limits test discovery to the JavaScript test files inside test.
Check eslint.config.js
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["src/**/*.js", "test/**/*.js", "*.config.js"],
rules: {
"prefer-const": "error",
},
},
]);
What to Compare
This configuration checks the source files. It also checks the tests and JavaScript configuration files.
Check dagger.toml
[modules.eslint]
source = "github.com/dagger/eslint@35a0cc0923fdf625608afb1c2b276e00262c0f81"
[modules.vitest]
source = "github.com/dagger/vitest@736010a059e07bd261d6097f8471e5c83a2b84aa"
What to Compare
This workspace installs the pinned ESLint module and the pinned Vitest module.
Keep dagger.lock
Dagger generates dagger.lock after installing the pinned modules. Keep its resolved OCI digest entries without hand-editing them because those entries vary by CPU architecture.
Your local quality gate now protects behavior and code quality with one command. Next, you will run the same workspace in GitHub Actions.
Run the Gate in GitHub Actions
Your local Dagger gate now runs the pinned Vitest and ESLint checks successfully from one command. The next goal is to make that same gate run after every push.
A local gate can still be skipped before code is shared. GitHub Actions closes that gap by invoking the checked-in Dagger workspace for pushes and pull requests.
In this step, get ready to:
- Add a workflow that invokes the pinned Dagger action.
- Publish the complete workspace to a public GitHub repository.
- Confirm the hosted job passes the same Vitest and ESLint checks.
Create the GitHub Actions workflow
A workflow file tells GitHub Actions which events start the gate. It also defines the hosted job that runs your Dagger workspace.
- Use your Linux application search to find Visual Studio Code.
- Start Visual Studio Code from the search result.
- Select the dagger-quality-gate folder in the folder picker.
- Create .github/workflows/quality-gate.yml from the file sidebar.
- Paste this workflow into .github/workflows/quality-gate.yml:
name: Dagger quality gate
on:
push:
pull_request:
permissions:
contents: read
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7.0.1
- name: Run Dagger checks
uses: dagger/dagger-for-github@v8.4.1
with:
check: "**"
version: "v1.0.0-beta.11"
What Does This Workflow Do?
- The push trigger starts the workflow when commits reach GitHub.
- The pull_request trigger starts the workflow for proposed changes.
- The contents: read permission lets the job read the repository without granting write access.
- The checks job runs on ubuntu-latest.
- The actions/checkout@v7.0.1 action loads the repository onto the hosted runner.
- The dagger/dagger-for-github@v8.4.1 action runs Dagger v1.0.0-beta.11.
- The ** check value executes every installed workspace check.
- Save .github/workflows/quality-gate.yml.
- Confirm the file sidebar shows quality-gate.yml inside .github/workflows.
Workflow File in the Wrong Place?
Confirm the complete path is .github/workflows/quality-gate.yml. GitHub only loads workflow files from .github/workflows.
Check whether the file sidebar nested the folders correctly. Help me check the location and contents of my GitHub Actions workflow.
✔️ Awesome, I've got everything!
Your saved workflow now runs every Dagger workspace check on pushes and pull requests.
ⓧ I'd like to double check the full code
Compare your complete .github/workflows/quality-gate.yml file with this version:
name: Dagger quality gate
on:
push:
pull_request:
permissions:
contents: read
jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7.0.1
- name: Run Dagger checks
uses: dagger/dagger-for-github@v8.4.1
with:
check: "**"
version: "v1.0.0-beta.11"
Publish the repository to GitHub
A public GitHub repository is the handoff point for your checked-in workflow. Standard GitHub-hosted runners are free for public repositories.
- Go to GitHub in your browser.
- Start a new repository from the repository menu.
- Enter dagger-quality-gate as the repository name.
- Set the repository visibility to Public.
- Leave README initialization disabled.
- Leave .gitignore initialization disabled.
- Leave the license selection empty.
- Create the repository.
You should see an empty repository page with instructions for pushing existing code.
- Copy the repository URL shown on the empty repository page: REMOTE-URL.
- Commit the complete workspace by running these commands in the terminal from earlier:
git add .
git commit -m "Add Dagger quality gate"
What Does This Commit Capture?
Git stages every project file. This includes the generated dagger.lock file and .github/workflows/quality-gate.yml.
The commit records one reproducible snapshot of the local gate. GitHub Actions receives that same snapshot after the push.
- Confirm the terminal reports a new commit on branch main.
Commit Not Created?
If Git asks for author identity, follow the instructions shown in the terminal. Rerun the commit commands after saving those details.
If the workflow is missing from the commit, confirm that you saved .github/workflows/quality-gate.yml inside dagger-quality-gate. Help me diagnose why my Git commit did not include the workflow.
A first push may ask you to authenticate with GitHub. Your sign-in details stay out of the project files.
- Complete GitHub authentication if Git requests it during the push.
- Connect the local repository to REMOTE-URL and push main by running these commands:
git remote add origin [[REMOTE_URL="REMOTE-URL"]]
git push -u origin main
What Does This Push Do?
The remote name origin links your local repository to its GitHub destination. The -u flag records the upstream branch for future pushes.
Pushing main sends the commit to GitHub. That push also triggers the new workflow.
Your terminal should show that main was sent to origin.
- Refresh the GitHub repository page.
You should see the complete quality-gate workspace on branch main. The file list should include dagger.lock and the .github directory.
Push Not Reaching GitHub?
Confirm that REMOTE-URL belongs to the public dagger-quality-gate repository. Complete any authentication request before retrying the push.
Check that your terminal is still inside the local dagger-quality-gate repository. Help me troubleshoot why my main branch is not reaching GitHub.
Verify the hosted quality gate
The checks job is the portability proof for your gate. This is where you compare the hosted check names with the successful local result from earlier.
Before you open the run, which two Dagger checks do you expect the checks job to execute?
- Select the Actions tab on the repository page.
- Enable repository workflows if GitHub shows an activation prompt.
- Open the workflow run triggered by your push.
- Select the checks job.
- Expand the Run Dagger checks step.
You should see the checks job finish successfully. The expanded trace should show the Vitest and ESLint checks passing under Dagger v1.0.0-beta.11.
Workflow Run Missing or Failing?
Confirm that .github/workflows/quality-gate.yml exists on branch main. Confirm that repository workflows are enabled.
If the job fails, expand the Run Dagger checks step to find the first failed operation. Help me diagnose my failing Dagger GitHub Actions job.
- Compare the hosted Vitest and ESLint check names with the successful local dagger check result from earlier.
You have now proved that the same pinned quality gate passes locally and on a GitHub-hosted runner.
Secret mission
Add a Formatting Check That Can Fix Itself
Your quality gate already catches broken behavior and risky code patterns. Add Prettier as a third check, trigger a real formatting failure, then apply the Dagger-generated changeset that repairs it. The unchanged GitHub Actions workflow will discover the new check automatically.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the project available, pause your pushes, or remove it completely. Public repositories using standard GitHub-hosted runners keep this project at $0.
Resources you used:
- Local dagger-quality-gate Git repository with its Dagger workspace.
- Public dagger-quality-gate GitHub repository with its GitHub Actions workflow.
Keep everything running
No action is needed. Choose this option if you want to keep demonstrating or extending your portable quality gate.
- Keep the local dagger-quality-gate repository for future experiments.
- Keep the public GitHub repository available as a working demonstration.
- Leave .github/workflows/quality-gate.yml enabled so future pushes continue to run the gate.
Pause - I'll come back to this later
This option preserves both repository copies without starting new workflow runs. Your project remains ready for the next change.
- Stop pushing commits to the public dagger-quality-gate repository.
- Leave .github/workflows/quality-gate.yml unchanged in both repository copies.
- Resume the gate later by pushing your next commit.
Delete - I don't want to use this again
Deleting both repository copies is permanent, so this option suits a finished project you no longer need. Your source files, commit history, and workflow disappear with them.
- Return to the public dagger-quality-gate repository on GitHub from earlier.
- Select Settings in the repository navigation.
- Select General in the settings sidebar.
- Find the Danger Zone section.
- Select Delete this repository.
- Enter the repository name when GitHub requests confirmation.
- Confirm the permanent deletion.
- Confirm that dagger-quality-gate no longer appears in your GitHub repository list.
The local repository is independent from the copy on GitHub. Removing its folder clears the project from your Linux device.
- Switch back to the terminal at the dagger-quality-gate repository root.
- Move above the repository and delete its folder by running this command:
cd .. && rm -rf dagger-quality-gate
What Does This Command Do?
The first part moves your shell above the repository. The second part permanently deletes dagger-quality-gate with every file inside it.
- List the remaining items in the parent directory by running this command:
ls
What Should You See?
The directory listing should no longer contain dagger-quality-gate. This confirms that the local repository has been removed.
Still See the Folder?
- Check that your terminal is in the directory directly above dagger-quality-gate.
- Delete the folder through your Linux file manager if the shell reports a permissions problem.
Help me safely remove the local project folder on Linux.
Nice Work!
Nice Work!
You did it! You built a portable JavaScript quality gate with Dagger. The same checks now protect your code locally and in GitHub Actions.
You've learned how to:
- Built a deployment-readiness function with a deliberate defect. You used Vitest to expose the wrong verdict before repairing the application logic.
- Created a containerized Dagger workspace that runs pinned Vitest and ESLint checks through one local command. You also observed unchanged setup work being reused across repeated runs.
- Ran a portable CI quality gate on a GitHub-hosted runner. Your existing workflow executed the same project-owned checks after every push.
- Extended the gate with a Prettier formatting check in the Secret Mission. You triggered a real formatting failure before applying the Dagger-generated changeset.
Ready to quiz yourself?