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.
Full-stack (Node + Python)
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:
Account-wide (Enterprise)
Certificates, proxy, DNS, VPN, commit signing, locale, resource limits, git identity, APT mirrors.
Applies to all orgs and all repos .
Org-wide
Language runtimes, package manager registry config, container registry, shared tooling.
Applies to all repos within the org .
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.
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
initialize :
- name : Install and configure WireGuard
run : |
sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq wireguard-tools
# Write the WireGuard config
echo "$WG_CONFIG_B64" | base64 -d \
| sudo tee /etc/wireguard/wg0.conf > /dev/null
sudo chmod 600 /etc/wireguard/wg0.conf
# Start the tunnel
sudo systemctl enable --now wg-quick@wg0
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)
Yarn Classic (v1)
Yarn Berry (v2+)
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"
maintenance :
- name : Configure Yarn Berry for private registry
run : |
yarn config set npmRegistryServer "$NPM_REGISTRY_URL"
yarn config set npmAuthToken "$REGISTRY_TOKEN"
# For scoped packages:
# yarn config set npmScopes.myorg.npmRegistryServer "https://npm.pkg.github.com"
# yarn config set npmScopes.myorg.npmAuthToken "$GITHUB_PACKAGES_TOKEN"
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 .