deno ci command overview
The `deno ci` command performs a reproducible install for CI environments, mirroring `npm ci`. It is used in CI scripts and Dockerfiles when you want a single, greppable command that is guaranteed to install exactly what the lockfile records with no drift, no leftover state, and no surprises.
deno ci basic usage
The basic usage of `deno ci` is simply: `deno ci`. It requires no additional arguments to execute.
deno ci is equivalent to rm -rf node_modules and deno install --frozen
The `deno ci` command is roughly equivalent to running `rm -rf node_modules` followed by `deno install --frozen`, but with stricter error handling and clearer failure modes.
deno ci requires a lockfile
The `deno ci` command requires a lockfile. If `deno.lock` is missing or unreadable, `deno ci` errors out instead of generating one. This guarantees that two runs of the same commit install the same versions.
deno ci wipes node_modules first
The `deno ci` command wipes the `node_modules` directory first. Any stale state from a previous CI step or a leaked development install is removed before installing, so the final tree reflects only what the lockfile resolved to.
deno ci locks resolution with --frozen flag
The `deno ci` command locks resolution with the `--frozen` flag. If `deno.json` or `package.json` has been edited without re-running `deno install` to refresh the lockfile, the command fails. This catches mistakes like bumping a version but forgetting to commit the lockfile before they reach production.
deno ci --prod flag
The `deno ci` command supports the `--prod` flag. When used, `--prod` skips `devDependencies` from `package.json`. This is useful for production Docker images and production installs on the host.
deno ci --skip-types flag
The `deno ci` command supports the `--skip-types` flag. When used, `--skip-types` drops `@types/*` packages from both `deno.json` imports and `package.json` dependencies. This is useful in deployment artifacts where types add weight and are not needed at runtime.
When to use deno ci vs deno install
Use `deno ci` in CI build/test/lint pipelines, production Docker images, and any scenario where the install must be reproducible and there is no expectation that the lockfile should change. Use `deno install` for local development with frequent edits, when adding or removing packages, or for first-time bootstrap when no lockfile exists yet.
deno ci GitHub Actions example
Example GitHub Actions workflow for testing with `deno ci`:
```yaml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- run: deno ci
- run: deno test
- run: deno lint
- run: deno fmt --check
```
deno ci production Dockerfile example
Example production Dockerfile using `deno ci`:
```dockerfile
FROM denoland/deno:2.8.0
WORKDIR /app
# Copy the manifest files first so install caches well
COPY deno.json deno.lock package.json* ./
RUN deno ci --prod
# Then copy the rest of the source
COPY . .
CMD ["deno", "run", "-A", "main.ts"]
```
Splitting the `COPY` into manifests first and source after lets Docker reuse the `deno ci` layer whenever dependencies are unchanged.
deno ci production install on host example
Example command for a production install on the host:
```sh
deno ci --prod --skip-types
```
This skips `devDependencies` from `package.json` with `--prod` and drops `@types/*` packages from both `deno.json` imports and `package.json` dependencies with `--skip-types`. This is useful in deployment artifacts where types add weight and are not needed at runtime.
deno ci error: deno.lock is missing
Error message: "deno.lock is missing". Likely cause: No lockfile is committed. Fix: Run `deno install` locally and commit the resulting `deno.lock`.
deno ci error: lockfile is out of date
Error message: "lockfile is out of date". Likely cause: A dependency was added or bumped in `deno.json` or `package.json` but the lockfile was not refreshed. Fix: Run `deno install` locally and commit the updated `deno.lock`.
deno ci error: build-script approval prompts in non-TTY CI
Error: Build-script approval prompts in non-TTY CI. Likely cause: A new npm package wants to run lifecycle scripts that have not been approved. Fix: Approve them locally with `deno approve-scripts` and commit the approvals.
deno ci command for installing locked dependencies
The `deno ci` command installs locked dependencies as a single reproducible step and handles npm lifecycle-script handling. It is available in Deno 2.8 and later. It is equivalent to `deno install --frozen` plus npm lifecycle-script handling.
Basic GitHub Actions workflow setup for Deno
A basic Deno CI pipeline in GitHub Actions starts with checking out the repository and installing Deno. The workflow uses `actions/checkout@v4` for the checkout step and `denoland/setup-deno@v2` for installing Deno. The deno-version can be specified, such as 'v2.x' for the latest stable Deno.
Cross-platform GitHub Actions matrix for Deno
To test Deno code across multiple operating systems (Linux, macOS, Windows), use a GitHub Actions strategy matrix with `os: [ubuntu-latest, macos-latest, windows-latest]` and run the same build steps on each OS in parallel.
Windows line ending issue with deno fmt in GitHub Actions
GitHub Actions has a known issue with handling Windows-style line endings (CRLF) that may cause issues when running `deno fmt` in pipelines with windows jobs. To prevent this, configure the Actions runner to use Linux-style line endings before running `actions/checkout@v4` by running: `git config --system core.autocrlf false` and `git config --system core.eol lf`.
Including canary Deno version in matrix strategy
To test with experimental or unstable Deno APIs, include a matrix job running the canary version of Deno. Use `deno-version: canary` and set `continue-on-error: true` to allow the job to complete even if the canary run fails.
Using if conditional in GitHub Actions to reduce repetition
Use the `if` conditional keyword in GitHub Actions to run certain steps only on specific OS runners. For example, `if: matrix.os == 'ubuntu-latest'` will run a step only on the Linux runner, useful for avoiding redundant coverage report generation across all platforms.
denoland/setup-deno cache option for caching dependencies
Set `cache: true` in the `denoland/setup-deno@v2` action to enable automatic caching of Deno dependencies between workflow runs. This preserves the cache directory between workflows, allowing subsequent runs to restore dependencies from cache instead of re-downloading them.
Default cache key for denoland/setup-deno
By default, the cache used by `denoland/setup-deno` is automatically keyed by the GitHub job_id, the runner OS and architecture, and a hash of the `deno.lock` files in the project. The default hash is `${{ hashFiles('**/deno.lock') }}`.
Customizing cache-hash in denoland/setup-deno
The `cache-hash` input parameter in `denoland/setup-deno@v2` allows customization of the default cache hash. Setting `cache-hash` implies `cache: true` and replaces the default cache-hash of `${{ hashFiles('**/deno.lock') }}` with a custom value, such as `${{ hashFiles('**/deno.json') }}`.
Updating deno.lock after dependency version changes
After updating a dependency version in deno.json, run `deno install --reload --frozen=false` locally to reload and update the lockfile. Changes in the lockfile should be committed so that when the workflow runs in the pipeline, the new cache will be created and used in subsequent runs.
GitHub Actions official Deno starter workflow
GitHub provides an official starter workflow for Deno at https://github.com/actions/starter-workflows/blob/main/ci/deno.yml. This can be added to a repository by opening the Actions tab in GitHub, choosing 'New workflow', and searching for 'Deno' to use the template.