Skip to main content
This page provides self-contained environment.yaml examples for common scenarios. Each example covers one concern — combine them to build your full configuration. For an introduction to environment.yaml syntax and concepts, see Environment Configuration. For syntax details, see the YAML Reference.
Secrets: Examples reference secrets via $SECRET_NAME. Configure these in Settings → Secrets before using an example. Each example includes a collapsible “Required secrets” section listing exactly which secrets to set up and what values they expect. Never hardcode credentials in your environment.yaml.

Quick reference: most common setups

The most frequently used configurations in one place. For the full list, see the sections below.
initialize: |
  npm install -g pnpm

maintenance: |
  pnpm install

knowledge:
  - name: lint
    contents: |
      Run `pnpm lint` to check for errors.
  - name: test
    contents: |
      Run `pnpm test` for the full suite.
initialize:
  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  - name: Sync dependencies
    run: uv sync

knowledge:
  - name: lint
    contents: |
      Run `uv run ruff check .` to lint.
  - name: test
    contents: |
      Run `uv run pytest` for the full suite.
initialize:
  - name: Install pnpm
    run: npm install -g pnpm
  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  - name: Frontend deps
    run: (cd frontend && pnpm install)
  - name: Backend deps
    run: (cd backend && uv sync)

knowledge:
  - name: structure
    contents: |
      - `frontend/` — React app (pnpm)
      - `backend/` — Python API (uv)
  - name: test
    contents: |
      Frontend: cd frontend && pnpm test
      Backend: cd backend && uv run pytest
initialize: |
  npm install -g pnpm

maintenance: |
  pnpm install

knowledge:
  - name: structure
    contents: |
      Monorepo managed with pnpm workspaces.
      - `packages/web` — Next.js frontend
      - `packages/api` — Express.js backend
      - `packages/shared` — Shared utilities
  - name: test
    contents: |
      Run `pnpm test` from the root for all packages.
      Run `pnpm --filter web test` for a specific package.

How the layers work

Environment configurations are applied in layers. Each layer builds on the previous one:
1

Account-wide (Enterprise)

Certificates, proxy, DNS, VPN, commit signing, locale, resource limits, git identity, APT mirrors. Applies to all orgs and all repos.
2

Org-wide

Language runtimes, package manager registry config, container registry, shared tooling. Applies to all repos within the org.
3

Repo-specific

Build commands, dependency install, test/lint commands, project-specific notes for Devin. Applies to a single repo.
Account-wide runs first, then org-wide, then repo-specific. Configure each example at the appropriate scope in Settings.

Module reference

Use this table to find the right example for your needs. Each module is independent — combine them freely.
ModuleLayerSection
CA certificatesEnterpriseCorporate CA certificate
Multiple CA certificatesEnterpriseMultiple CA certificates
HTTP/HTTPS proxyEnterpriseHTTP/HTTPS proxy
Authenticated proxyEnterpriseAuthenticated proxy
CA cert + proxyEnterpriseCA certificate + proxy (combined)
VPN (OpenVPN / WireGuard)EnterpriseVPN connection
Custom DNSEnterpriseCustom DNS resolution
GPG commit signingEnterpriseGPG commit signing
Git identity + SSH keysEnterpriseGit identity and SSH keys
System packagesEnterpriseInstall system packages
Environment variablesEnterpriseCustom environment variables
Locale & timezoneEnterpriseLocale and timezone
Resource limitsEnterpriseResource limits (ulimits)
APT mirrorEnterpriseAPT mirror replacement
Java + MavenOrgJava + Maven with a private registry
Java + GradleOrgJava + Gradle with a private registry
Python + pip/uvOrgPython + pip/uv with a private registry
Python + PoetryOrgPython + Poetry with a private registry
Node.js + npm (scoped)OrgNode.js + npm with a scoped private registry
Node.js + npm (full mirror)OrgNode.js + npm with a full private registry mirror
Node.js + pnpmOrgNode.js + pnpm with a private registry
Node.js + YarnOrgNode.js + Yarn with a private registry
GoOrgGo with a private module proxy
.NET + NuGetOrg.NET + NuGet with a private feed
DockerOrgDocker with a private container registry
Rust + CargoOrgRust + Cargo with a private registry
Ruby + BundlerOrgRuby + Bundler with a private gem server
PHP + ComposerOrgPHP + Composer with a private registry
AWS CodeArtifactOrgAWS CodeArtifact token refresh
Node.js projectRepoStandard Node.js project
Python projectRepoStandard Python project
Java projectRepoStandard Java project
Go projectRepoStandard Go project
Rust projectRepoStandard Rust project
MonorepoRepoMonorepo with multiple services
Multi-JDK monorepoRepoMonorepo with multiple JDK versions
Knowledge entriesRepoRich knowledge entries
Pre-commit hooksRepoPre-commit hooks

Enterprise / account-wide examples

These examples configure machine-level infrastructure that applies across all orgs and repos. Set them in Enterprise Settings (for account-wide) or Settings > Environment > Organization-wide setup (for org-wide).

Corporate CA certificate

Your organization uses a private certificate authority for internal services. Devin needs the root certificate to reach internal registries and tools over HTTPS.
  • CORP_ROOT_CA_B64 — Base64-encoded PEM certificate from your corporate CA. Generate with: cat corp-root-ca.crt | base64 -w0
initialize:
  - name: Install corporate CA certificate
    run: |
      echo "$CORP_ROOT_CA_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
      sudo update-ca-certificates

      # Let Node.js trust the corporate CA
      echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
        | sudo tee /etc/profile.d/node-ca.sh > /dev/null

Multiple CA certificates

Some organizations have separate CAs for different internal services (e.g., one for the artifact registry, another for an internal Git server).
  • CA_CERT_REGISTRY_B64 — Base64-encoded PEM certificate for the artifact registry CA
  • CA_CERT_GIT_B64 — Base64-encoded PEM certificate for the Git server CA
initialize:
  - name: Install corporate CA certificates
    run: |
      # Decode and install each certificate
      echo "$CA_CERT_REGISTRY_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/registry-ca.crt > /dev/null
      echo "$CA_CERT_GIT_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/git-ca.crt > /dev/null

      sudo update-ca-certificates

      # Node.js: combine certs into a single bundle
      cat /usr/local/share/ca-certificates/registry-ca.crt \
          /usr/local/share/ca-certificates/git-ca.crt \
        > /tmp/corp-ca-bundle.crt
      echo "export NODE_EXTRA_CA_CERTS=/tmp/corp-ca-bundle.crt" \
        | sudo tee /etc/profile.d/node-ca.sh > /dev/null

HTTP/HTTPS proxy

Your organization routes outbound traffic through a corporate proxy.
  • CORP_HTTP_PROXY — HTTP proxy URL (e.g., http://proxy.corp.internal:8080)
  • CORP_HTTPS_PROXY — HTTPS proxy URL (e.g., http://proxy.corp.internal:8080)
  • CORP_NO_PROXY — Comma-separated hosts to bypass the proxy (e.g., localhost,127.0.0.1,.corp.internal,10.0.0.0/8)
initialize:
  - name: Configure system-wide HTTP/HTTPS proxy
    run: |
      cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
      export http_proxy="$CORP_HTTP_PROXY"
      export https_proxy="$CORP_HTTPS_PROXY"
      export no_proxy="$CORP_NO_PROXY"
      export HTTP_PROXY="$CORP_HTTP_PROXY"
      export HTTPS_PROXY="$CORP_HTTPS_PROXY"
      export NO_PROXY="$CORP_NO_PROXY"
      PROXY
      source /etc/profile.d/proxy.sh

maintenance:
  - name: Configure git proxy
    run: |
      git config --global http.proxy "$CORP_HTTP_PROXY"
      git config --global https.proxy "$CORP_HTTPS_PROXY"
Set CORP_NO_PROXY to a comma-separated list of hosts that should bypass the proxy, such as localhost,127.0.0.1,.corp.internal,10.0.0.0/8.

Authenticated proxy

If your proxy requires a username and password, embed the credentials in the proxy URL.
  • PROXY_USER — Proxy authentication username
  • PROXY_PASS — Proxy authentication password
  • PROXY_HOST — Proxy hostname (e.g., proxy.corp.internal)
  • PROXY_PORT — Proxy port (e.g., 8080)
  • CORP_NO_PROXY — Comma-separated hosts to bypass the proxy
initialize:
  - name: Configure authenticated HTTP/HTTPS proxy
    run: |
      cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
      export http_proxy="http://$PROXY_USER:$PROXY_PASS@$PROXY_HOST:$PROXY_PORT"
      export https_proxy="http://$PROXY_USER:$PROXY_PASS@$PROXY_HOST:$PROXY_PORT"
      export no_proxy="$CORP_NO_PROXY"
      export HTTP_PROXY="$http_proxy"
      export HTTPS_PROXY="$https_proxy"
      export NO_PROXY="$CORP_NO_PROXY"
      PROXY
      source /etc/profile.d/proxy.sh

maintenance:
  - name: Configure git proxy
    run: |
      git config --global http.proxy "http://$PROXY_USER:$PROXY_PASS@$PROXY_HOST:$PROXY_PORT"
      git config --global https.proxy "http://$PROXY_USER:$PROXY_PASS@$PROXY_HOST:$PROXY_PORT"

CA certificate + proxy (combined)

The most common enterprise baseline — install a corporate CA certificate and configure a system-wide proxy.
  • CORP_ROOT_CA_B64 — Base64-encoded PEM certificate from your corporate CA
  • CORP_HTTP_PROXY — HTTP proxy URL
  • CORP_HTTPS_PROXY — HTTPS proxy URL
  • CORP_NO_PROXY — Comma-separated hosts to bypass the proxy
initialize:
  - name: Install corporate CA certificate
    run: |
      echo "$CORP_ROOT_CA_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
      sudo update-ca-certificates
      echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
        | sudo tee /etc/profile.d/node-ca.sh > /dev/null

  - name: Configure system-wide proxy
    run: |
      cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
      export http_proxy="$CORP_HTTP_PROXY"
      export https_proxy="$CORP_HTTPS_PROXY"
      export no_proxy="$CORP_NO_PROXY"
      export HTTP_PROXY="$CORP_HTTP_PROXY"
      export HTTPS_PROXY="$CORP_HTTPS_PROXY"
      export NO_PROXY="$CORP_NO_PROXY"
      PROXY
      source /etc/profile.d/proxy.sh

maintenance:
  - name: Configure git proxy
    run: |
      git config --global http.proxy "$CORP_HTTP_PROXY"
      git config --global https.proxy "$CORP_HTTPS_PROXY"

VPN connection

Your private registries, Git servers, or other internal services are only reachable over VPN. This must run before other modules that need network access to internal resources.
OpenVPN:
  • VPN_CONFIG_B64 — Base64-encoded OpenVPN config file (.ovpn). Generate with: cat corp.ovpn | base64 -w0
  • VPN_AUTH_USER (optional) — VPN username, if your VPN requires username/password auth
  • VPN_AUTH_PASS (optional) — VPN password
WireGuard:
  • WG_CONFIG_B64 — Base64-encoded WireGuard config file. Generate with: cat wg0.conf | base64 -w0
initialize:
  - name: Install and configure OpenVPN
    run: |
      sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openvpn

      # Write the VPN config
      sudo mkdir -p /etc/openvpn/client
      echo "$VPN_CONFIG_B64" | base64 -d \
        | sudo tee /etc/openvpn/client/corp.conf > /dev/null

      # If VPN requires username/password auth
      if [ -n "${VPN_AUTH_USER:-}" ] && [ -n "${VPN_AUTH_PASS:-}" ]; then
        printf '%s\n%s\n' "$VPN_AUTH_USER" "$VPN_AUTH_PASS" \
          | sudo tee /etc/openvpn/client/auth.txt > /dev/null
        sudo chmod 600 /etc/openvpn/client/auth.txt
        echo "auth-user-pass /etc/openvpn/client/auth.txt" \
          | sudo tee -a /etc/openvpn/client/corp.conf > /dev/null
      fi

      # Start the VPN tunnel
      sudo systemctl daemon-reload
      sudo systemctl enable --now openvpn-client@corp

      # Wait for the tunnel to come up
      for i in $(seq 1 30); do
        if ip link show tun0 >/dev/null 2>&1; then break; fi
        sleep 1
      done
For more details on VPN setup, see VPN Configuration.

Custom DNS resolution

Your internal services use private DNS names that aren’t resolvable by public DNS.
initialize:
  - name: Configure custom DNS resolution
    run: |
      # Add internal hostnames
      cat << 'HOSTS' | sudo tee -a /etc/hosts > /dev/null
      10.0.1.50  nexus.corp.internal
      10.0.1.51  git.corp.internal
      10.0.1.52  artifactory.corp.internal
      HOSTS

      # Optionally configure custom nameservers
      sudo mkdir -p /etc/systemd/resolved.conf.d
      cat << 'DNS' | sudo tee /etc/systemd/resolved.conf.d/corp.conf > /dev/null
      [Resolve]
      DNS=10.0.0.53 10.0.0.54
      Domains=corp.internal
      DNS
      sudo systemctl restart systemd-resolved || true

GPG commit signing

Your organization requires all Git commits to be signed.
  • GPG_PRIVATE_KEY_B64 — Base64-encoded GPG private key. Generate with: gpg --export-secret-keys <key-id> | base64 -w0
initialize:
  - name: Prepare GPG and git signing config
    run: |
      # Allow GPG to work without a TTY
      echo 'export GPG_TTY=$(tty)' | sudo tee -a /etc/profile.d/gpg.sh > /dev/null

      # Pre-configure git to sign commits (key imported in maintenance)
      git config --global commit.gpgsign true
      git config --global tag.gpgsign true

maintenance:
  - name: Import GPG signing key
    run: |
      echo "$GPG_PRIVATE_KEY_B64" | base64 -d | gpg --batch --import

      # Set the signing key ID
      KEY_ID=$(gpg --list-secret-keys --keyid-format long 2>/dev/null \
        | grep sec | head -1 | awk '{print $2}' | cut -d'/' -f2)
      git config --global user.signingkey "$KEY_ID"

Git identity and SSH keys

Configure Git user identity and SSH keys for accessing private repositories over SSH.
  • GIT_USER_NAME — Git author name (e.g., Devin AI)
  • GIT_USER_EMAIL — Git author email (e.g., devin@company.com)
  • SSH_PRIVATE_KEY_B64 — Base64-encoded SSH private key. Generate with: cat ~/.ssh/id_ed25519 | base64 -w0
  • SSH_KNOWN_HOSTS_B64 — Base64-encoded known hosts. Generate with: ssh-keyscan git.corp.internal | base64 -w0
  • SSH_CONFIG_B64 (optional) — Base64-encoded SSH config file for custom host aliases
initialize:
  - name: Prepare SSH directory and git identity
    run: |
      # Set git identity
      git config --global user.name "$GIT_USER_NAME"
      git config --global user.email "$GIT_USER_EMAIL"

      # Prepare SSH directory
      mkdir -p ~/.ssh && chmod 700 ~/.ssh

      # Enable git-lfs if needed
      # git lfs install

maintenance:
  - name: Install SSH keys
    run: |
      # Install SSH private key (in maintenance so it's freshly loaded each session)
      echo "$SSH_PRIVATE_KEY_B64" | base64 -d > ~/.ssh/id_ed25519
      chmod 600 ~/.ssh/id_ed25519

      # Add known hosts for your Git server
      echo "$SSH_KNOWN_HOSTS_B64" | base64 -d >> ~/.ssh/known_hosts

      # Optionally install a custom SSH config
      if [ -n "${SSH_CONFIG_B64:-}" ]; then
        echo "$SSH_CONFIG_B64" | base64 -d > ~/.ssh/config
        chmod 600 ~/.ssh/config
      fi
Generate the known hosts entry for your Git server with ssh-keyscan git.corp.internal | base64 -w0.

Install system packages

Your project needs system-level packages that aren’t in the default Devin image (e.g., native libraries for image processing or PDF generation).
initialize:
  - name: Install system packages
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
        libpq-dev \
        libmagickwand-dev \
        poppler-utils \
        ffmpeg

Custom environment variables

Set persistent environment variables that should be available in every session. The recommended approach is to write KEY=VALUE lines to the $ENVRC file. Variables written to $ENVRC are automatically exported for all subsequent steps and the Devin session (similar to GitHub Actions’ $GITHUB_ENV).
initialize:
  - name: Set custom environment variables
    run: |
      echo "CORPORATE_ENV=production" >> $ENVRC
      echo "DEFAULT_REGION=us-east-1" >> $ENVRC
      echo "MAX_RETRIES=3" >> $ENVRC
You can also write env vars to /etc/profile.d/ scripts for system-wide availability:
cat << 'ENVVARS' | sudo tee /etc/profile.d/custom-env.sh > /dev/null
export CORPORATE_ENV=production
export DEFAULT_REGION=us-east-1
ENVVARS
Both approaches work. $ENVRC is simpler and recommended for most cases.

Locale and timezone

Default base images may have broken locale settings. Configure locale and timezone to prevent warnings from build tools, Java, Python, and Git.
initialize:
  - name: Configure locale and timezone
    run: |
      sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq locales

      # Generate and set the locale
      sudo sed -i 's/^# *en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen
      sudo locale-gen
      sudo update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8

      cat << 'LOCALE' | sudo tee /etc/profile.d/locale.sh > /dev/null
      export LANG="en_US.UTF-8"
      export LC_ALL="en_US.UTF-8"
      LOCALE

      # Set timezone
      sudo timedatectl set-timezone UTC 2>/dev/null || \
        sudo ln -sfn /usr/share/zoneinfo/UTC /etc/localtime

Resource limits (ulimits)

Java, Gradle, and Node.js builds frequently hit the default 1024 open-file limit. Raise it to prevent build failures.
initialize:
  - name: Raise resource limits
    run: |
      cat << 'LIMITS' | sudo tee /etc/security/limits.d/99-devin.conf > /dev/null
      *    soft    nofile    65536
      *    hard    nofile    65536
      *    soft    nproc     65536
      *    hard    nproc     65536
      LIMITS

      # Also set the kernel max
      echo "fs.file-max = 65536" | sudo tee /etc/sysctl.d/99-devin-filemax.conf > /dev/null
      sudo sysctl -p /etc/sysctl.d/99-devin-filemax.conf 2>/dev/null || true

APT mirror replacement

In air-gapped or restricted environments, replace the default Ubuntu APT sources with an internal mirror.
  • APT_MIRROR_URL — URL of your internal APT mirror (e.g., https://artifactory.example.com/artifactory/ubuntu-remote)
initialize:
  - name: Replace APT sources with internal mirror
    run: |
      # Backup original sources
      sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak

      # Replace all Ubuntu mirrors with your internal mirror
      sudo sed -i "s|http://archive.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list
      sudo sed -i "s|http://security.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list

      # Disable third-party sources that won't be reachable
      # sudo mv /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/disabled/ 2>/dev/null || true

      sudo apt-get update -qq
Common APT mirror URL patterns:
  • Artifactory: https://artifactory.example.com/artifactory/ubuntu-remote
  • Nexus: https://nexus.example.com/repository/ubuntu-proxy

Org-wide examples

These examples install language runtimes and configure package managers to use private registries. Set them in Settings > Environment > Organization-wide setup.
If your private registry uses a corporate CA, make sure the CA certificate is installed at the enterprise level first. The org-level configuration below assumes HTTPS trust is already established.
Credential configuration belongs in maintenance, not initialize. Steps that write secrets (registry passwords, auth tokens) into config files should use maintenance so credentials are freshly loaded each session. The secrets file is removed before the machine image is saved, so config files written during initialize won’t have valid credentials when sessions start.

Java + Maven with a private registry

Install JDK and configure Maven to mirror all dependency resolution through your private registry (e.g., Artifactory, Nexus).
JDK 17 is pre-installed on Devin’s base image. Skip the install step below if the default OpenJDK 17 is sufficient — you only need the Maven install and registry configuration.
  • MAVEN_REGISTRY_URL — URL of your Maven registry (e.g., https://artifactory.example.com/artifactory/maven-virtual)
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install JDK 17
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
      echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
        | sudo tee /etc/profile.d/java.sh > /dev/null

  - name: Install Maven
    run: |
      MAVEN_VERSION=3.9.9
      curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
        | sudo tar -xz -C /opt
      sudo ln -sf /opt/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn

maintenance:
  - name: Configure Maven for private registry
    run: |
      mkdir -p ~/.m2
      cat > ~/.m2/settings.xml << EOF
      <settings>
        <mirrors>
          <mirror>
            <id>private-registry</id>
            <mirrorOf>*</mirrorOf>
            <url>$MAVEN_REGISTRY_URL</url>
          </mirror>
        </mirrors>
        <servers>
          <server>
            <id>private-registry</id>
            <username>$REGISTRY_USER</username>
            <password>$REGISTRY_PASS</password>
          </server>
        </servers>
      </settings>
      EOF
Common registry URL patterns for Maven:
  • Artifactory: https://artifactory.example.com/artifactory/maven-virtual
  • Nexus: https://nexus.example.com/repository/maven-public
  • Azure Artifacts: https://pkgs.dev.azure.com/org/project/_packaging/feed/maven/v1
  • GitHub Packages: https://maven.pkg.github.com
  • GitLab: https://gitlab.example.com/api/v4/groups/<group-id>/-/packages/maven
  • AWS CodeArtifact: https://<domain>.d.codeartifact.<region>.amazonaws.com/maven/<repo>

Java + Gradle with a private registry

Install JDK and configure Gradle to resolve all dependencies through your private registry.
JDK 17 is pre-installed on Devin’s base image. Skip the JDK install step if the default is sufficient.
  • GRADLE_REGISTRY_URL — URL of your Gradle/Maven registry
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install JDK 17
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
      echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
        | sudo tee /etc/profile.d/java.sh > /dev/null

  - name: Install Gradle
    run: |
      GRADLE_VERSION=8.12
      curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
        -o /tmp/gradle.zip
      sudo unzip -qo /tmp/gradle.zip -d /opt
      sudo ln -sf /opt/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle
      rm /tmp/gradle.zip

maintenance:
  - name: Configure Gradle for private registry
    run: |
      mkdir -p ~/.gradle
      cat > ~/.gradle/init.gradle << EOF
      allprojects {
          repositories {
              maven {
                  url "$GRADLE_REGISTRY_URL"
                  credentials {
                      username = "$REGISTRY_USER"
                      password = "$REGISTRY_PASS"
                  }
                  allowInsecureProtocol = false
              }
          }
      }
      EOF

Python + pip/uv with a private registry

Configure pip and uv to resolve packages from your private PyPI registry (e.g., Nexus, Artifactory).
  • PYPI_REGISTRY_HOST — Hostname of your PyPI registry (e.g., artifactory.example.com/artifactory/api/pypi/pypi-virtual)
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  - name: Configure pip for private registry
    run: |
      mkdir -p ~/.config/pip
      cat > ~/.config/pip/pip.conf << EOF
      [global]
      index-url = https://$REGISTRY_USER:$REGISTRY_PASS@${PYPI_REGISTRY_HOST}/simple
      trusted-host = ${PYPI_REGISTRY_HOST}
      EOF

  - name: Configure uv for private registry
    run: |
      # uv respects pip.conf, but you can also set it explicitly
      echo "export UV_INDEX_URL=https://$REGISTRY_USER:$REGISTRY_PASS@${PYPI_REGISTRY_HOST}/simple" \
        | sudo tee /etc/profile.d/uv-registry.sh > /dev/null
Common registry URL patterns for PyPI:
  • Artifactory: https://artifactory.example.com/artifactory/api/pypi/pypi-virtual/simple
  • Nexus: https://nexus.example.com/repository/pypi-group/simple
  • Azure Artifacts: https://pkgs.dev.azure.com/org/project/_packaging/feed/pypi/simple/
  • GitLab: https://gitlab.example.com/api/v4/groups/<group-id>/-/packages/pypi/simple
  • AWS CodeArtifact: https://<domain>.d.codeartifact.<region>.amazonaws.com/pypi/<repo>/simple

Python + Poetry with a private registry

Configure Poetry to resolve packages from a private PyPI registry.
  • PYPI_REGISTRY_HOST — Hostname of your PyPI registry
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install Poetry
    run: curl -sSL https://install.python-poetry.org | python3 -

maintenance:
  - name: Configure Poetry for private registry
    run: |
      poetry config repositories.private "https://${PYPI_REGISTRY_HOST}/simple"
      poetry config http-basic.private "$REGISTRY_USER" "$REGISTRY_PASS"

      # Optionally set the private registry as the default source
      # poetry source add --priority=primary private "https://${PYPI_REGISTRY_HOST}/simple"

Node.js + npm with a scoped private registry

Configure npm to resolve scoped packages (e.g., @myorg/*) from a private registry like GitHub Packages, while public packages continue to come from the default npm registry.
  • GITHUB_PACKAGES_TOKEN — Personal access token or GitHub App token with read:packages scope
maintenance:
  - name: Configure npm scoped registry
    run: |
      npm config set @myorg:registry https://npm.pkg.github.com
      npm config set //npm.pkg.github.com/:_authToken $GITHUB_PACKAGES_TOKEN
Replace @myorg with your npm scope. Common private registry URLs:
  • GitHub Packages: https://npm.pkg.github.com
  • Artifactory: https://artifactory.example.com/artifactory/api/npm/npm-virtual
  • Nexus: https://nexus.example.com/repository/npm-group
  • GitLab: https://gitlab.example.com/api/v4/packages/npm
  • AWS CodeArtifact: https://<domain>.d.codeartifact.<region>.amazonaws.com/npm/<repo>

Node.js + npm with a full private registry mirror

Route all npm packages through your private registry (not just scoped packages).
  • NPM_REGISTRY_URL — Full URL of your npm registry (e.g., https://artifactory.example.com/artifactory/api/npm/npm-virtual)
  • NPM_REGISTRY_HOST — Hostname only, without protocol (e.g., artifactory.example.com)
  • REGISTRY_TOKEN — npm auth token for the registry
maintenance:
  - name: Configure npm to use private registry
    run: |
      npm config set registry $NPM_REGISTRY_URL
      npm config set //${NPM_REGISTRY_HOST}/:_authToken $REGISTRY_TOKEN
      npm config set strict-ssl true

Node.js + pnpm with a private registry

Configure pnpm to resolve packages from a private registry.
pnpm is pre-installed on Devin’s base image. You can skip the install step and only configure the registry.
  • NPM_REGISTRY_URL — Full URL of your npm registry
  • NPM_REGISTRY_HOST — Hostname only, without protocol
  • REGISTRY_TOKEN — npm auth token for the registry
initialize:
  - name: Install pnpm
    run: npm install -g pnpm

maintenance:
  - name: Configure pnpm for private registry
    run: |
      pnpm config set registry $NPM_REGISTRY_URL
      pnpm config set //${NPM_REGISTRY_HOST}/:_authToken $REGISTRY_TOKEN

Node.js + Yarn with a private registry

Configure Yarn (Classic v1 or Berry v2+) to resolve packages from a private registry.
Yarn Classic (v1) is pre-installed on Devin’s base image. Skip the install step if you only need v1.
  • NPM_REGISTRY_URL — Full URL of your npm/Yarn registry
  • REGISTRY_TOKEN — Auth token for the registry (Berry only)
initialize:
  - name: Install Yarn Classic
    run: npm install -g yarn

maintenance:
  - name: Configure Yarn for private registry
    run: |
      yarn config set registry "$NPM_REGISTRY_URL"
      # For scoped packages:
      # yarn config set @myorg:registry "https://npm.pkg.github.com"

Go with a private module proxy

Install Go and configure it to resolve modules through a private module proxy (e.g., Athens, Artifactory, or a GOPROXY endpoint).
  • GO_PROXY_URL — URL of your Go module proxy (e.g., https://athens.corp.internal)
  • GIT_TOKEN — Personal access token for private Git repos that host Go modules
initialize:
  - name: Install Go
    run: |
      GO_VERSION=1.23.5
      ARCH=$(dpkg --print-architecture)
      curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
        | sudo tar -xz -C /usr/local
      echo 'export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"' \
        | sudo tee /etc/profile.d/golang.sh > /dev/null

  - name: Configure Go for private modules
    run: |
      cat << 'GOENV' | sudo tee /etc/profile.d/go-private.sh > /dev/null
      export GOPROXY="$GO_PROXY_URL,direct"
      export GONOSUMCHECK="corp.internal/*,github.com/myorg/*"
      export GOPRIVATE="corp.internal/*,github.com/myorg/*"
      GOENV

maintenance:
  - name: Authenticate Go private modules
    run: |
      git config --global url."https://$GIT_TOKEN@github.com/myorg/".insteadOf "https://github.com/myorg/"
Common Go proxy URL patterns:
  • Artifactory: https://artifactory.example.com/artifactory/go-virtual
  • Nexus: https://nexus.example.com/repository/go-proxy
  • Athens: https://athens.corp.internal

.NET + NuGet with a private feed

Install the .NET SDK and configure NuGet to resolve packages from a private feed (e.g., Azure Artifacts, Artifactory).
  • NUGET_FEED_URL — URL of your NuGet feed (e.g., https://pkgs.dev.azure.com/org/project/_packaging/feed/nuget/v3/index.json)
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install .NET SDK
    run: |
      curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
      chmod +x /tmp/dotnet-install.sh
      sudo /tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/local/dotnet
      echo 'export DOTNET_ROOT=/usr/local/dotnet' \
        | sudo tee /etc/profile.d/dotnet.sh > /dev/null
      echo 'export PATH="$DOTNET_ROOT:$DOTNET_ROOT/tools:$PATH"' \
        | sudo tee -a /etc/profile.d/dotnet.sh > /dev/null
      rm /tmp/dotnet-install.sh

maintenance:
  - name: Configure NuGet for private feed
    run: |
      dotnet nuget add source "$NUGET_FEED_URL" \
        --name private-feed \
        --username "$REGISTRY_USER" \
        --password "$REGISTRY_PASS" \
        --store-password-in-clear-text

      # Optionally disable the default nuget.org source
      # dotnet nuget disable source nuget.org
Common NuGet feed URLs:
  • Azure Artifacts: https://pkgs.dev.azure.com/org/project/_packaging/feed/nuget/v3/index.json
  • Artifactory: https://artifactory.example.com/artifactory/api/nuget/v3/nuget-virtual
  • GitHub Packages: https://nuget.pkg.github.com/myorg/index.json
  • Nexus: https://nexus.example.com/repository/nuget-hosted/index.json

Docker with a private container registry

Configure Docker to authenticate with a private container registry.
  • DOCKER_MIRROR_URL (optional) — URL of your Docker Hub mirror (e.g., https://mirror.corp.internal)
  • DOCKER_REGISTRY_URL — URL of your private container registry (e.g., registry.corp.internal:5000)
  • DOCKER_REGISTRY_USER — Registry username
  • DOCKER_REGISTRY_PASS — Registry password or API token
initialize:
  - name: Create Docker config directory
    run: sudo mkdir -p /etc/docker

maintenance:
  - name: Configure Docker for private registry
    run: |
      # Configure registry mirror (optional — routes Docker Hub pulls through your registry)
      cat << EOF | sudo tee /etc/docker/daemon.json > /dev/null
      {
        "registry-mirrors": ["$DOCKER_MIRROR_URL"]
      }
      EOF
      sudo systemctl restart docker || true

      # Log in to the private container registry
      echo "$DOCKER_REGISTRY_PASS" | docker login "$DOCKER_REGISTRY_URL" \
        --username "$DOCKER_REGISTRY_USER" \
        --password-stdin
Common container registry URLs:
  • Amazon ECR: <account-id>.dkr.ecr.<region>.amazonaws.com
  • Azure Container Registry: <name>.azurecr.io
  • Google Artifact Registry: <region>-docker.pkg.dev
  • GitHub Container Registry: ghcr.io
  • GitLab Container Registry: registry.gitlab.example.com
  • Nexus: https://nexus.example.com:8443
  • JFrog: <name>.jfrog.io

Rust + Cargo with a private registry

Install Rust and configure Cargo to resolve crates from a private registry.
Rust (via rustup) and Cargo are pre-installed on Devin’s base image. Skip the install step if the default stable toolchain is sufficient — you only need the registry configuration.
  • CARGO_REGISTRY_INDEX — URL of the private registry index (e.g., sparse+https://cargo.corp.internal/api/v1/crates/)
  • CARGO_REGISTRY_TOKEN — Auth token for the private registry
initialize:
  - name: Install Rust
    run: |
      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
        | sh -s -- -y --default-toolchain stable
      echo 'source "$HOME/.cargo/env"' \
        | sudo tee /etc/profile.d/rust.sh > /dev/null

maintenance:
  - name: Configure Cargo for private registry
    run: |
      mkdir -p ~/.cargo
      cat > ~/.cargo/config.toml << EOF
      [registries.private]
      index = "$CARGO_REGISTRY_INDEX"
      token = "$CARGO_REGISTRY_TOKEN"

      [source.crates-io]
      replace-with = "private"

      [source.private]
      registry = "$CARGO_REGISTRY_INDEX"
      EOF
If you only need to add a private registry without replacing crates.io, remove the [source.crates-io] and [source.private] sections and use cargo install --registry private or [dependencies] my-crate = { version = "1.0", registry = "private" } in Cargo.toml.

Ruby + Bundler with a private gem server

Install Ruby and configure Bundler to resolve gems from a private gem server.
  • GEM_SERVER_URL — URL of your private gem server (e.g., https://artifactory.example.com/artifactory/api/gems/gems-virtual)
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install Ruby
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ruby-full

maintenance:
  - name: Configure Bundler for private gem server
    run: |
      bundle config set mirror.https://rubygems.org "$GEM_SERVER_URL"
      bundle config set "$GEM_SERVER_URL" "$REGISTRY_USER:$REGISTRY_PASS"
Common gem server URL patterns:
  • Artifactory: https://artifactory.example.com/artifactory/api/gems/gems-virtual
  • Nexus: https://nexus.example.com/repository/rubygems-proxy
  • Gemfury: https://gem.fury.io/<org>

AWS CodeArtifact token refresh

AWS CodeArtifact tokens expire after 12 hours. Use maintenance to refresh the token at the start of every session. This example configures npm, pip, and Maven to use CodeArtifact.
awscli is pre-installed on Devin’s base image. You only need the token refresh and registry configuration.
  • AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY — IAM credentials with codeartifact:GetAuthorizationToken and sts:GetServiceBearerToken permissions
  • CA_DOMAIN — Your CodeArtifact domain name
  • CA_DOMAIN_OWNER — AWS account ID that owns the domain
  • CA_REGION — AWS region (e.g., us-east-1)
  • CA_NPM_REPO, CA_PYPI_REPO, CA_MAVEN_REPO — Repository names for each ecosystem
maintenance:
  - name: Refresh CodeArtifact auth token
    run: |
      # Get a fresh token (valid for 12 hours)
      export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
        --domain $CA_DOMAIN \
        --domain-owner $CA_DOMAIN_OWNER \
        --region $CA_REGION \
        --query authorizationToken \
        --output text)

      CA_ENDPOINT="https://${CA_DOMAIN}-${CA_DOMAIN_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com"

      # Configure npm
      npm config set registry "${CA_ENDPOINT}/npm/${CA_NPM_REPO}/"
      npm config set "//${CA_DOMAIN}-${CA_DOMAIN_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com/npm/${CA_NPM_REPO}/:_authToken" "$CODEARTIFACT_AUTH_TOKEN"

      # Configure pip
      mkdir -p ~/.config/pip
      cat > ~/.config/pip/pip.conf << EOF
      [global]
      index-url = https://aws:${CODEARTIFACT_AUTH_TOKEN}@${CA_DOMAIN}-${CA_DOMAIN_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com/pypi/${CA_PYPI_REPO}/simple/
      EOF

      # Configure Maven (optional)
      mkdir -p ~/.m2
      cat > ~/.m2/settings.xml << EOF
      <settings>
        <servers>
          <server>
            <id>codeartifact</id>
            <username>aws</username>
            <password>${CODEARTIFACT_AUTH_TOKEN}</password>
          </server>
        </servers>
        <mirrors>
          <mirror>
            <id>codeartifact</id>
            <mirrorOf>*</mirrorOf>
            <url>${CA_ENDPOINT}/maven/${CA_MAVEN_REPO}/</url>
          </mirror>
        </mirrors>
      </settings>
      EOF

PHP + Composer with a private registry

Install PHP and configure Composer to resolve packages from a private Packagist or Satis registry.
  • COMPOSER_REGISTRY_URL — URL of your private Composer registry (e.g., https://repo.packagist.com/<org>)
  • REGISTRY_USER — Registry username
  • REGISTRY_PASS — Registry password or API token
initialize:
  - name: Install PHP and Composer
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
        php-cli php-mbstring php-xml php-curl unzip

      # Install Composer
      curl -sS https://getcomposer.org/installer | php
      sudo mv composer.phar /usr/local/bin/composer

maintenance:
  - name: Configure Composer for private registry
    run: |
      composer config --global repositories.private \
        composer "$COMPOSER_REGISTRY_URL"

      # Authenticate with the registry
      composer config --global http-basic.$(echo "$COMPOSER_REGISTRY_URL" \
        | sed 's|https\?://||;s|/.*||') "$REGISTRY_USER" "$REGISTRY_PASS"

      # Optionally disable the default packagist.org
      # composer config --global repositories.packagist false
Common Composer registry URL patterns:
  • Artifactory: https://artifactory.example.com/artifactory/api/composer/packagist-virtual
  • Nexus: https://nexus.example.com/repository/packagist-proxy
  • Private Packagist: https://repo.packagist.com/<org>
  • Satis: https://satis.corp.internal

Repository-specific examples

These examples configure per-repo build steps, dependency management, and knowledge entries. Set them in Settings > Environment > [your repo].

Standard Node.js project

A typical Node.js project with lint, test, and build commands.
initialize: |
  npm install -g pnpm

maintenance: |
  pnpm install

knowledge:
  - name: lint
    contents: |
      Run `pnpm lint` to check for errors.
      Run `pnpm lint --fix` to auto-fix.
  - name: test
    contents: |
      Run `pnpm test` for the full test suite.
      Run `pnpm test -- --watch` during development.
  - name: build
    contents: |
      Run `pnpm build` to create a production build.
      Output goes to the `dist/` directory.

Standard Python project

A Python project using uv for dependency management.
initialize:
  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  - name: Sync dependencies
    run: uv sync

knowledge:
  - name: lint
    contents: |
      Run `uv run ruff check .` to lint.
      Run `uv run ruff format .` to format.
  - name: test
    contents: |
      Run `uv run pytest` for the full test suite.
      Run `uv run pytest -x` to stop on first failure.

Standard Java project

A Java project using Maven for dependency management.
maintenance:
  - name: Resolve dependencies
    run: mvn dependency:resolve -U -q

knowledge:
  - name: build
    contents: |
      Run `mvn clean package` to build.
      Run `mvn clean package -DskipTests` to build without tests.
  - name: test
    contents: |
      Run `mvn test` for unit tests.
      Run `mvn verify` for integration tests.
  - name: lint
    contents: |
      Run `mvn checkstyle:check` for style checks.
      Run `mvn spotbugs:check` for bug detection.

Standard Go project

A Go project with standard tooling.
maintenance:
  - name: Download dependencies
    run: go mod download

knowledge:
  - name: build
    contents: |
      Run `go build ./...` to build all packages.
      Run `go build -o bin/app ./cmd/app` to build the main binary.
  - name: test
    contents: |
      Run `go test ./...` for all tests.
      Run `go test -race ./...` to include race detection.
      Run `go test -v ./pkg/... -run TestSpecific` for a specific test.
  - name: lint
    contents: |
      Run `golangci-lint run` for linting (if installed).
      Run `go vet ./...` for basic static analysis.

Standard Rust project

A Rust project using Cargo.
maintenance:
  - name: Fetch dependencies
    run: cargo fetch

knowledge:
  - name: build
    contents: |
      Run `cargo build` for a debug build.
      Run `cargo build --release` for a release build.
  - name: test
    contents: |
      Run `cargo test` for all tests.
      Run `cargo test -- --test-threads=1` for sequential execution.
  - name: lint
    contents: |
      Run `cargo clippy -- -D warnings` for lint checks.
      Run `cargo fmt --check` to verify formatting.

Monorepo with multiple services

A monorepo with separate frontend and backend services that have different package managers.
initialize:
  - name: Install pnpm
    run: npm install -g pnpm
  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  - name: Install frontend dependencies
    run: (cd packages/frontend && pnpm install)
  - name: Install backend dependencies
    run: (cd packages/backend && uv sync)
  - name: Build shared library
    run: (cd packages/shared && pnpm install && pnpm build)

knowledge:
  - name: structure
    contents: |
      This is a monorepo with three packages:
      - `packages/frontend` — React app (TypeScript, pnpm)
      - `packages/backend` — Python API (FastAPI, uv)
      - `packages/shared` — Shared TypeScript utilities (must be built before frontend)
  - name: frontend
    contents: |
      Run `cd packages/frontend && pnpm dev` to start the dev server.
      Run `cd packages/frontend && pnpm lint` to lint.
      Run `cd packages/frontend && pnpm test` to test.
  - name: backend
    contents: |
      Run `cd packages/backend && uv run uvicorn app.main:app --reload` to start the API.
      Run `cd packages/backend && uv run ruff check .` to lint.
      Run `cd packages/backend && uv run pytest` to test.
Use subshells (cd dir && command) instead of cd dir && command so the working directory resets between steps.

Monorepo with multiple JDK versions

A Java monorepo where different services require different JDK versions. Install both JDKs at setup, then use knowledge entries to tell Devin which JAVA_HOME to use for each service.
initialize:
  - name: Install JDK 17 (primary)
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
      echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
        | sudo tee /etc/profile.d/java.sh > /dev/null

  - name: Install JDK 11 (legacy service)
    run: |
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-11-jdk-headless

maintenance:
  - name: Warm dependency caches
    run: |
      export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
      (cd services/api && ./gradlew dependencies --refresh-dependencies)

      export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
      (cd services/legacy && ./gradlew dependencies --refresh-dependencies)

knowledge:
  - name: build_api
    contents: |
      Build the API service (JDK 17):
        JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
        cd services/api && ./gradlew clean build
  - name: build_legacy
    contents: |
      Build the legacy service (JDK 11):
        JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 \
        cd services/legacy && ./gradlew clean build
  - name: test_all
    contents: |
      Run tests for all services:
        JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
        (cd services/api && ./gradlew test)

        JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 \
        (cd services/legacy && ./gradlew test)

Pre-commit hooks

If your project uses pre-commit hooks, install them in maintenance so they’re ready for every session.
initialize:
  - name: Install pre-commit
    run: pip install pre-commit

maintenance:
  - name: Install pre-commit hooks
    run: pre-commit install --install-hooks
If the project already has pre-commit as a dev dependency (e.g., in pyproject.toml), skip the initialize step and use uv run pre-commit install --install-hooks or pipx run pre-commit install --install-hooks in maintenance instead.

Rich knowledge entries

Document your project’s architecture, conventions, and workflows in knowledge entries.
knowledge:
  - name: architecture
    contents: |
      This is a microservices application:
      - `api-gateway/` — Express.js reverse proxy (port 3000)
      - `auth-service/` — JWT authentication service (port 3001)
      - `user-service/` — User CRUD service (port 3002)
      - `shared/` — Shared protobuf definitions and utilities

      Services communicate via gRPC. The API gateway is the only public-facing service.

  - name: conventions
    contents: |
      - All API responses use the `{ data, error, meta }` envelope format
      - Database migrations are in `migrations/` and run with `npm run migrate`
      - Environment-specific config is in `config/{env}.json`
      - Feature flags are managed via LaunchDarkly (SDK key in $LD_SDK_KEY)

  - name: testing
    contents: |
      Unit tests: `npm test`
      Integration tests: `npm run test:integration` (requires Docker for Postgres)
      E2E tests: `npm run test:e2e` (requires all services running)

      Coverage report: `npm run test:coverage` (must be > 80% for CI to pass)

  - name: deployment
    contents: |
      CI/CD runs on GitHub Actions. Merges to `main` auto-deploy to staging.
      Production deploys require a manual approval step in the Actions UI.
      Docker images are pushed to ECR: 123456789.dkr.ecr.us-east-1.amazonaws.com/

Combined examples

These examples show how enterprise and org-level configurations combine. In practice, you’d split these across scopes — they’re shown together here for reference.

Full enterprise stack

A complete enterprise environment: corporate CA certificate, proxy, Java (Maven), Python (pip/uv), Node.js (npm), and Docker — all pointed at a single Artifactory instance.
Network & trust (account-wide):
  • CORP_ROOT_CA_B64 — Base64-encoded corporate CA certificate
  • CORP_HTTP_PROXY — HTTP proxy URL
  • CORP_HTTPS_PROXY — HTTPS proxy URL
  • CORP_NO_PROXY — Hosts to bypass proxy
Registry credentials (org-wide):
  • ARTIFACTORY_USER — Artifactory username
  • ARTIFACTORY_TOKEN — Artifactory API token or password
  • ARTIFACTORY_MAVEN_URL — Maven repository URL (e.g., https://artifactory.example.com/artifactory/maven-virtual)
  • ARTIFACTORY_PYPI_URL — PyPI repository URL (e.g., https://user:token@artifactory.example.com/artifactory/api/pypi/pypi-virtual/simple)
  • ARTIFACTORY_NPM_URL — npm repository URL (e.g., https://artifactory.example.com/artifactory/api/npm/npm-virtual)
  • ARTIFACTORY_DOCKER_URL — Docker registry URL (e.g., artifactory.example.com)
This would typically be split across three scopes:
  • Account-wide (initialize): Certificate and proxy
  • Org-wide (initialize): Language runtime installs
  • Org-wide (maintenance): Registry credentials (refreshed each session)
Shown here combined for reference:
initialize:
  # ── Account-wide: network and trust ──────────────────────────────────────

  - name: Install corporate CA certificate
    run: |
      echo "$CORP_ROOT_CA_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
      sudo update-ca-certificates
      echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
        | sudo tee /etc/profile.d/node-ca.sh > /dev/null

  - name: Configure system-wide proxy
    run: |
      cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
      export http_proxy="$CORP_HTTP_PROXY"
      export https_proxy="$CORP_HTTPS_PROXY"
      export no_proxy="$CORP_NO_PROXY"
      export HTTP_PROXY="$CORP_HTTP_PROXY"
      export HTTPS_PROXY="$CORP_HTTPS_PROXY"
      export NO_PROXY="$CORP_NO_PROXY"
      PROXY
      source /etc/profile.d/proxy.sh

  # ── Org-wide: language runtimes ──────────────────────────────────────────

  - name: Install JDK 17 + Maven
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
      echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
        | sudo tee /etc/profile.d/java.sh > /dev/null

      MAVEN_VERSION=3.9.9
      curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
        | sudo tar -xz -C /opt
      sudo ln -sf /opt/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn

  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  # ── Account-wide: git proxy (refreshed each session) ───────────────────

  - name: Configure git proxy
    run: |
      git config --global http.proxy "$CORP_HTTP_PROXY"
      git config --global https.proxy "$CORP_HTTPS_PROXY"

  # ── Org-wide: registry credentials (refreshed each session) ──────────────

  - name: Configure Maven → Artifactory
    run: |
      mkdir -p ~/.m2
      cat > ~/.m2/settings.xml << EOF
      <settings>
        <mirrors>
          <mirror>
            <id>artifactory</id>
            <mirrorOf>*</mirrorOf>
            <url>$ARTIFACTORY_MAVEN_URL</url>
          </mirror>
        </mirrors>
        <servers>
          <server>
            <id>artifactory</id>
            <username>$ARTIFACTORY_USER</username>
            <password>$ARTIFACTORY_TOKEN</password>
          </server>
        </servers>
      </settings>
      EOF

  - name: Configure pip/uv → Artifactory PyPI
    run: |
      mkdir -p ~/.config/pip
      cat > ~/.config/pip/pip.conf << EOF
      [global]
      index-url = $ARTIFACTORY_PYPI_URL
      trusted-host = $(echo "$ARTIFACTORY_PYPI_URL" | sed 's|https\?://||;s|/.*||')
      EOF

      echo "export UV_INDEX_URL=$ARTIFACTORY_PYPI_URL" \
        | sudo tee /etc/profile.d/uv-registry.sh > /dev/null

  - name: Configure npm → Artifactory
    run: |
      npm config set registry "$ARTIFACTORY_NPM_URL"
      REGISTRY_HOST=$(echo "$ARTIFACTORY_NPM_URL" | sed 's|https\?://||;s|/.*||')
      npm config set "//${REGISTRY_HOST}/:_authToken" "$ARTIFACTORY_TOKEN"

  - name: Configure Docker → Artifactory
    run: |
      echo "$ARTIFACTORY_TOKEN" | docker login "$ARTIFACTORY_DOCKER_URL" \
        --username "$ARTIFACTORY_USER" \
        --password-stdin
In this example, all registries point at the same Artifactory instance but use different URL paths. Each package ecosystem has its own endpoint format — Maven, PyPI, npm, and Docker URLs are all different even for the same registry.

Multi-language with different registries

When different languages use different private registries (e.g., Maven from Nexus, npm from GitHub Packages, Python from Artifactory).
  • NEXUS_MAVEN_URL — Nexus Maven repository URL
  • NEXUS_USER — Nexus username
  • NEXUS_PASS — Nexus password
  • GITHUB_PACKAGES_TOKEN — GitHub personal access token with read:packages scope
  • ARTIFACTORY_USER — Artifactory username
  • ARTIFACTORY_TOKEN — Artifactory API token
  • GIT_TOKEN — Personal access token for Go private modules
maintenance:
  # Maven → Nexus
  - name: Configure Maven → Nexus
    run: |
      mkdir -p ~/.m2
      cat > ~/.m2/settings.xml << EOF
      <settings>
        <mirrors>
          <mirror>
            <id>nexus</id>
            <mirrorOf>*</mirrorOf>
            <url>$NEXUS_MAVEN_URL</url>
          </mirror>
        </mirrors>
        <servers>
          <server>
            <id>nexus</id>
            <username>$NEXUS_USER</username>
            <password>$NEXUS_PASS</password>
          </server>
        </servers>
      </settings>
      EOF

  # npm → GitHub Packages (scoped)
  - name: Configure npm → GitHub Packages
    run: |
      npm config set @myorg:registry https://npm.pkg.github.com
      npm config set //npm.pkg.github.com/:_authToken $GITHUB_PACKAGES_TOKEN

  # Python → Artifactory
  - name: Configure pip → Artifactory
    run: |
      mkdir -p ~/.config/pip
      cat > ~/.config/pip/pip.conf << EOF
      [global]
      index-url = https://$ARTIFACTORY_USER:$ARTIFACTORY_TOKEN@artifactory.example.com/artifactory/api/pypi/pypi-virtual/simple
      EOF

  # Go → private modules via git
  - name: Configure Go private modules
    run: |
      git config --global url."https://$GIT_TOKEN@github.com/myorg/".insteadOf "https://github.com/myorg/"

Air-gapped environment with private mirrors

In a fully air-gapped environment, Devin cannot reach any public URLs. All tools, runtimes, and packages must come from internal mirrors.
Certificates:
  • CORP_ROOT_CA_B64 — Base64-encoded corporate CA certificate
Mirror access:
  • APT_MIRROR_URL — Internal Ubuntu APT mirror URL
  • MIRROR_USER — Mirror authentication username
  • MIRROR_PASS — Mirror authentication password
  • JDK_TARBALL_URL — URL to download JDK tarball from internal mirror
  • NODE_TARBALL_URL — URL to download Node.js tarball from internal mirror
Package registries:
  • INTERNAL_MAVEN_URL — Internal Maven registry URL
  • INTERNAL_NPM_URL — Internal npm registry URL
  • INTERNAL_PYPI_URL — Internal PyPI registry URL
initialize:
  - name: Install corporate CA certificate
    run: |
      echo "$CORP_ROOT_CA_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
      sudo update-ca-certificates

  - name: Replace apt sources with internal mirror
    run: |
      sudo sed -i "s|http://archive.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list
      sudo sed -i "s|http://security.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list
      sudo apt-get update -qq

  - name: Install JDK from internal mirror
    run: |
      # Download JDK tarball from internal artifact store
      curl -fsSL -u "$MIRROR_USER:$MIRROR_PASS" "$JDK_TARBALL_URL" \
        | sudo tar -xz -C /usr/local
      sudo ln -sf /usr/local/jdk-17.*/bin/java /usr/local/bin/java
      sudo ln -sf /usr/local/jdk-17.*/bin/javac /usr/local/bin/javac
      echo "export JAVA_HOME=$(ls -d /usr/local/jdk-17.*)" \
        | sudo tee /etc/profile.d/java.sh > /dev/null

  - name: Install Node.js from internal mirror
    run: |
      curl -fsSL -u "$MIRROR_USER:$MIRROR_PASS" "$NODE_TARBALL_URL" \
        | sudo tar -xz -C /usr/local --strip-components=1

maintenance:
  - name: Configure all package managers for internal registry
    run: |
      # Maven
      mkdir -p ~/.m2
      cat > ~/.m2/settings.xml << EOF
      <settings>
        <mirrors>
          <mirror>
            <id>internal</id>
            <mirrorOf>*</mirrorOf>
            <url>$INTERNAL_MAVEN_URL</url>
          </mirror>
        </mirrors>
        <servers>
          <server>
            <id>internal</id>
            <username>$MIRROR_USER</username>
            <password>$MIRROR_PASS</password>
          </server>
        </servers>
      </settings>
      EOF

      # npm
      npm config set registry "$INTERNAL_NPM_URL"

      # pip
      mkdir -p ~/.config/pip
      cat > ~/.config/pip/pip.conf << EOF
      [global]
      index-url = $INTERNAL_PYPI_URL
      EOF
In air-gapped environments, all tools Devin needs (language runtimes, CLI tools, etc.) must be available on your internal mirrors. Public registries and download sites are unreachable.

VPN + certificates + proxy + languages

A comprehensive enterprise setup combining VPN connectivity with certificates, proxy, and multi-language support. This is the recommended order of operations.
VPN:
  • VPN_CONFIG_B64 — Base64-encoded OpenVPN config file
Network & trust:
  • CORP_ROOT_CA_B64 — Base64-encoded corporate CA certificate
  • CORP_HTTP_PROXY — HTTP proxy URL
  • CORP_HTTPS_PROXY — HTTPS proxy URL
  • CORP_NO_PROXY — Hosts to bypass proxy
Registry credentials:
  • MAVEN_REGISTRY_URL — Maven registry URL
  • NPM_REGISTRY_URL — npm registry URL
  • PYPI_REGISTRY_HOST — PyPI registry hostname
  • REGISTRY_USER — Registry username (for Maven and pip)
  • REGISTRY_PASS — Registry password (for Maven and pip)
  • REGISTRY_TOKEN — npm auth token
initialize:
  # 1. VPN — must come first so internal resources are reachable
  - name: Establish VPN connection
    run: |
      sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openvpn
      sudo mkdir -p /etc/openvpn/client
      echo "$VPN_CONFIG_B64" | base64 -d \
        | sudo tee /etc/openvpn/client/corp.conf > /dev/null
      sudo systemctl daemon-reload
      sudo systemctl enable --now openvpn-client@corp
      for i in $(seq 1 30); do
        if ip link show tun0 >/dev/null 2>&1; then break; fi
        sleep 1
      done

  # 2. DNS — resolve internal hostnames
  - name: Configure DNS
    run: |
      sudo mkdir -p /etc/systemd/resolved.conf.d
      cat << 'DNS' | sudo tee /etc/systemd/resolved.conf.d/corp.conf > /dev/null
      [Resolve]
      DNS=10.0.0.53
      Domains=corp.internal
      DNS
      sudo systemctl restart systemd-resolved || true

  # 3. Certificates — trust internal CAs
  - name: Install CA certificate
    run: |
      echo "$CORP_ROOT_CA_B64" | base64 -d \
        | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
      sudo update-ca-certificates
      echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
        | sudo tee /etc/profile.d/node-ca.sh > /dev/null

  # 4. Proxy — route traffic through corporate proxy
  - name: Configure proxy
    run: |
      cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
      export http_proxy="$CORP_HTTP_PROXY"
      export https_proxy="$CORP_HTTPS_PROXY"
      export no_proxy="$CORP_NO_PROXY"
      export HTTP_PROXY="$CORP_HTTP_PROXY"
      export HTTPS_PROXY="$CORP_HTTPS_PROXY"
      export NO_PROXY="$CORP_NO_PROXY"
      PROXY
      source /etc/profile.d/proxy.sh

  # 5. Language runtimes
  - name: Install JDK 17
    run: |
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
      echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
        | sudo tee /etc/profile.d/java.sh > /dev/null

  - name: Install Node.js tooling
    run: npm install -g pnpm

  - name: Install uv
    run: curl -LsSf https://astral.sh/uv/install.sh | sh

maintenance:
  - name: Configure git proxy
    run: |
      git config --global http.proxy "$CORP_HTTP_PROXY"
      git config --global https.proxy "$CORP_HTTPS_PROXY"

  - name: Configure Maven
    run: |
      mkdir -p ~/.m2
      cat > ~/.m2/settings.xml << EOF
      <settings>
        <mirrors>
          <mirror>
            <id>corp</id>
            <mirrorOf>*</mirrorOf>
            <url>$MAVEN_REGISTRY_URL</url>
          </mirror>
        </mirrors>
        <servers>
          <server>
            <id>corp</id>
            <username>$REGISTRY_USER</username>
            <password>$REGISTRY_PASS</password>
          </server>
        </servers>
      </settings>
      EOF

  - name: Configure npm
    run: |
      npm config set registry "$NPM_REGISTRY_URL"
      NPM_HOST=$(echo "$NPM_REGISTRY_URL" | sed 's|https\?://||;s|/.*||')
      npm config set "//${NPM_HOST}/:_authToken" "$REGISTRY_TOKEN"

  - name: Configure pip/uv
    run: |
      mkdir -p ~/.config/pip
      cat > ~/.config/pip/pip.conf << EOF
      [global]
      index-url = https://$REGISTRY_USER:$REGISTRY_PASS@${PYPI_REGISTRY_HOST}/simple
      EOF
      echo "export UV_INDEX_URL=https://$REGISTRY_USER:$REGISTRY_PASS@${PYPI_REGISTRY_HOST}/simple" \
        | sudo tee /etc/profile.d/uv-registry.sh > /dev/null
Order matters for initialize steps. VPN must come first (so internal hosts are reachable), then DNS (so names resolve), then certificates (so HTTPS works), then proxy (so traffic routes correctly), and finally language runtimes (which may download from internal mirrors).

Tips for writing good configurations

  • Test commands in a session first — run commands manually in a Devin session before adding them to your config. This is faster than waiting for a full build cycle.
  • Use initialize for install-once tools, maintenance for deps — anything that takes minutes to install (compilers, large binaries, global tools) belongs in initialize. Quick dependency commands (npm install, uv sync) go in maintenance.
  • Keep maintenance commands fast — aim for under 2 minutes. These run at the start of every session.
  • Use $ENVRC for environment variables — don’t write to .bashrc or .profile. $ENVRC is the supported mechanism for setting variables across steps and sessions.
  • Name your steps — the expanded form with name fields makes build log failures much easier to identify.
  • Use subshells for monorepos(cd packages/foo && npm install) runs in a subshell so subsequent steps aren’t affected by the directory change.
For more on syntax and execution details, see the YAML Reference. For troubleshooting build failures, see Troubleshooting & FAQ.