How We United 8 Developers Across Restricted Environments Using Azure VMs and Dev Containers

How We United 8 Developers Across Restricted Environments Using Azure VMs and Dev Containers

Introduction: When Traditional Solutions Hit a Wall

Last month, I found myself facing a challenge that I’m sure many of you have encountered: How do you enable seamless collaboration for a development team when half of them work in a locked-down environment where they can’t install any development tools, and the other half can’t access the client’s systems?

Our team of eight developers was tasked with building a proof-of-concept (PoC) for an AI-powered agentic system using Microsoft’s AutoGen framework. Here’s the kicker: this was a 3-week PoC sprint bringing together two teams from different organizations who had never worked together before. We needed a collaborative environment that could be spun up quickly, require minimal setup effort, and allow everyone to hit the ground running from day one.

The project requirements were complex enough, but the real challenge? Four developers worked from a highly restricted corporate environment where installing Python, VS Code, or any development tools was strictly prohibited. The remaining four worked from our offices but couldn’t access the client’s internal systems directly.

We tried the usual approaches:

  • RDP connections: Blocked by security policies
  • VPN access: Denied due to compliance requirements
  • Local development with file sharing: Immediate sync issues and “works on my machine” problems
  • Cloud IDEs: Didn’t meet the client’s security requirements

Just when we thought we’d have to resort to the dreaded “develop locally and pray it works in production” approach, we discovered a solution that not only solved our immediate problem but revolutionized how we approach distributed development.

The Architecture That Worked For Us

Here’s a visual representation of what we built, everyone had to work on their personal (non-corporate) laptops for this to work.

flowchart TD
    A["💻 8 Developers
Personal Laptops Only

4 from Restricted Environment
4 from External Environment"] B["🚫 The Problem

Corporate PCs: No dev tools
Company laptops: No client access"] C["☁️ Azure VM Solution
Standard D8s v3

8 vCPUs, 32GB RAM
Individual user accounts"] D["🐳 Dev Container

Python 3.11 + AutoGen
All dependencies included
Consistent environment"] E["📁 Shared Workspace

Project repository
Datasets and models
Real-time collaboration"] A -->|"SSH + VS Code Remote"| C B -.->|"Solved by"| A C --> D D --> E style A fill:#4CAF50,stroke:#2E7D32,color:#fff,stroke-width:3px style B fill:#f44336,stroke:#c62828,color:#fff,stroke-width:2px style C fill:#2196F3,stroke:#1565C0,color:#fff,stroke-width:3px style D fill:#9C27B0,stroke:#6A1B9A,color:#fff,stroke-width:3px style E fill:#FF9800,stroke:#E65100,color:#fff,stroke-width:3px

Lets check out how this was built and setup…

The Deep Dive: How We Built It

Step 1: Provisioning the Azure VM

We started with a Linux VM in Azure. After some testing, we settled on a Standard D8s v3 instance (8 vCPUs, 32 GB RAM) which provided enough resources for all eight developers to work simultaneously without performance issues.

1
2
3
4
5
6
7
8
9
# VM Creation (simplified for clarity)
az vm create \
--resource-group DevEnvironmentRG \
--name SharedDevVM \
--image Ubuntu2204 \
--size Standard_D8s_v3 \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-address-allocation static

Step 2: User Account Architecture

Instead of having everyone share a single account (security nightmare!), we created individual Linux user accounts for each developer. This approach gave us:

  • Audit trails: We could track who did what and when
  • Personalized environments: Each developer could customize their shell, aliases, and local configs
  • Security isolation: Problems with one account wouldn’t affect others
  • Resource monitoring: We could track resource usage per developer if needed

Here’s how we automated user creation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
# create_dev_users.sh

DEVELOPERS=("alice" "bob" "charlie" "david" "eve" "frank" "grace" "henry")

for dev in "${DEVELOPERS[@]}"; do
# Create user with home directory
sudo useradd -m -s /bin/bash $dev

# Create .ssh directory
sudo mkdir -p /home/$dev/.ssh
sudo chmod 700 /home/$dev/.ssh

# Set up for SSH key authentication
sudo touch /home/$dev/.ssh/authorized_keys
sudo chmod 600 /home/$dev/.ssh/authorized_keys

# Set ownership
sudo chown -R $dev:$dev /home/$dev/.ssh

# Add to docker group (for container access)
sudo usermod -aG docker $dev

echo "Created user: $dev"
done

Step 3: Certificate-Based Authentication

Password authentication over the internet? Not on our watch. We implemented certificate-based SSH authentication for each developer:

1
2
3
4
# On each developer's local machine
ssh-keygen -t ed25519 -C "developer@project" -f ~/.ssh/project_dev_key

# The public key was then added to their respective authorized_keys file on the VM

The beauty of this approach:

  • No passwords to remember or rotate
  • Certificates could be revoked instantly if needed
  • Multi-factor authentication could be added via Azure AD if required
  • Worked seamlessly even from the restricted environment (SSH client was available)

Step 4: VS Code Remote Development Magic

This is where the magic happened. VS Code’s Remote-SSH extension turned our Linux VM into a powerful development environment. Each developer configured their VS Code with:

1
2
3
4
5
6
// .ssh/config on developer machine
Host azure-dev-vm
HostName <VM-PUBLIC-IP>
User alice
IdentityFile ~/.ssh/project_dev_key
ForwardAgent yes

Once connected, developers had the full VS Code experience:

  • IntelliSense working perfectly
  • Debugging capabilities
  • Extension support
  • Integrated terminal
  • Git integration

But we didn’t stop there…

Step 5: The Dev Container Revolution

Here’s where we went from “good” to “game-changing.” We created a Dev Container that encapsulated our entire development environment. This meant:

No more “pip install” parties: Everything was pre-installed
No more version conflicts: Everyone used the exact same versions
No more missing dependencies: If it worked for one, it worked for all

Our .devcontainer/devcontainer.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{
"name": "Autogen Development Environment",
"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
"features": {
"ghcr.io/devcontainers/features/anaconda:1": {},
"ghcr.io/devcontainers/features/azure-cli:1": {
"version": "latest"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"moby": true,
"installDockerBuildx": true,
"installDockerComposeSwitch": true,
"version": "latest",
"dockerDashComposeVersion": "v2"
},
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
"packages": [
"tig"
]
}
},
"customizations": {
"vscode": {
"extensions": [
"github.copilot",
"ms-azuretools.vscode-docker",
"ms-python.python",
"ms-python.vscode-pylance",
"redhat.vscode-yaml",
"editorconfig.editorconfig",
"ms-azure-devops.azure-pipelines",
"ms-azure-load-testing.microsoft-testing",
"ms-azuretools.azure-dev",
"ms-azuretools.vscode-azure-github-copilot",
"ms-azuretools.vscode-azureappservice",
"ms-azuretools.vscode-azurecontainerapps",
"ms-azuretools.vscode-azurefunctions",
"eamodio.gitlens",
"ms-azuretools.vscode-azurelogicapps",
"ms-azuretools.vscode-azureresourcegroups",
"ms-azuretools.vscode-azurestaticwebapps",
"ms-azuretools.vscode-azurestorage",
"ms-azuretools.vscode-azurevirtualmachines",
"ms-azuretools.vscode-bicep",
"ms-azuretools.vscode-containers",
"ms-azuretools.vscode-cosmosdb",
"ms-azuretools.vscode-docker",
"ms-python.black-formatter",
"ms-python.debugpy",
"ms-python.isort",
"ms-python.pylint",
"ms-python.python",
"ms-python.vscode-pylance",
"ms-vscode.azure-repos",
"ms-vscode.azurecli",
"ms-azuretools.azure-dev",
"ms-azuretools.vscode-azure-github-copilot"
]
}
}
// "postCreateCommand": "pip3 install --user -r requirements.txt",
}

Step 6: Shared Resources and Collaboration

With everyone working on the same VM, we could leverage shared resources effectively:

1
2
3
4
5
6
7
8
9
10
11
12
# Shared directories for common resources
/home/shared/
├── datasets/ # Common datasets for AI training
├── models/ # Shared model artifacts
├── configs/ # Shared configuration files
└── scripts/ # Utility scripts

# Permissions set for group collaboration
sudo groupadd developers
sudo usermod -a -G developers alice bob charlie... # all developers
sudo chown -R :developers /home/shared
sudo chmod -R 775 /home/shared

The Unexpected Benefits

1. Lightning-Fast Onboarding

Our typical onboarding process used to take 2-3 days:

  • Day 1: Install Python, configure environment
  • Day 2: Debug dependency issues, version conflicts
  • Day 3: Finally start actual development

With our new setup:

  • Hour 1: Receive SSH certificate and connection instructions
  • Hour 2: Connect VS Code, open project in container
  • Hour 3: Writing production code

That’s a 94% reduction in onboarding time!

2. Compute Power Democracy

Previously, developers with older laptops struggled with AI model training and testing. Now everyone had access to:

  • 8 vCPUs for parallel processing
  • 32 GB RAM for large datasets
  • Fast SSD storage for quick I/O
  • Azure’s network backbone for downloading models and datasets

3. Cost Optimization That Surprised Finance

Our finance team loved this approach:

  • Traditional approach: 8 high-spec laptops = ~$16,000
  • Our approach: 1 Azure VM = ~$400/month

Even accounting for the VM running 24/7, we saved money within the first year.

4. Security Without Suffering

The restricted environment developers could finally contribute without compromising security:

  • No software installed on their local machines
  • All code remained in the cloud
  • Audit logs for every action
  • Easy to revoke access when project ended

Real-World Results: The AutoGen Project

Let me share some specific wins from our AutoGen AI agent project:

Development Velocity

  • Before: 2-3 features per sprint (too much time on environment issues)
  • After: 8-10 features per sprint (focus on actual development)

Code Quality

  • Before: “Works on my machine” was a daily phrase
  • After: If it worked in the dev container, it worked everywhere

Team Morale

  • Before: Frustration with environment setup and restrictions
  • After: Developers focused on solving interesting AI problems

Specific AutoGen Benefits

Working with AutoGen requires multiple AI models, API keys, and complex configurations. Our setup handled this beautifully:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Shared configuration file accessible to all
# /home/shared/configs/autogen_config.py

config_list = [
{
"model": "gpt-4",
"api_key": os.environ.get("OPENAI_API_KEY"),
},
{
"model": "gpt-3.5-turbo",
"api_key": os.environ.get("OPENAI_API_KEY"),
}
]

# Each developer could test with the same models and configurations
# No "I don't have API access" blockers

Lessons Learned and Best Practices

What Worked Well

  1. Start with more resources than you think you need: We initially tried a smaller VM and hit performance issues. Better to scale down than suffer with poor performance.

  2. Invest time in the Dev Container setup: Every hour spent perfecting the container saved days of debugging later.

  3. Document everything: We created a comprehensive wiki with:

    • Connection instructions
    • Troubleshooting guides
    • Best practices for shared development
    • Git workflow for the shared environment
  4. Regular backups: We automated daily backups of the entire VM and home directories.

Challenges We Faced

  1. Concurrent file editing: We needed clear Git workflows to prevent conflicts

    • Solution: Feature branches and frequent commits
  2. Resource contention: Occasionally, one developer’s process would hog resources

    • Solution: Implemented resource limits using cgroups
  3. SSH connection drops: Some developers faced connection issues

    • Solution: Configured SSH keep-alive and implemented tmux for session persistence

Security Considerations

Don’t forget these crucial security aspects:

1
2
3
4
5
6
7
8
9
10
11
12
# Implement fail2ban for SSH protection
sudo apt-get install fail2ban

# Configure firewall rules
sudo ufw allow from <OFFICE_IP_RANGE> to any port 22
sudo ufw enable

# Regular security updates
sudo unattended-upgrades

# Audit logging
sudo apt-get install auditd

Conclusion

What started as a desperate attempt to enable collaboration across restricted environments turned into a revolutionary approach to distributed development. By leveraging Azure VMs, Dev Containers, and VS Code Remote Development, we not only solved our immediate problem but discovered a solution that offers:

  • 94% faster onboarding for new team members
  • Significant cost savings compared to traditional hardware approaches
  • Enhanced security without sacrificing developer productivity
  • True collaboration through shared resources and environments
  • Consistent development experience across all team members

The key insight was recognizing that personal laptops could serve as the bridge between restricted corporate environments and cloud-based development infrastructure. Sometimes the best solutions come from thinking outside the traditional corporate IT box.

Whether you’re dealing with similar restrictions or simply want to improve your team’s development experience, this architecture pattern could be the game-changer you’re looking for. The combination of Azure infrastructure, containerized development environments, and modern remote development tools creates a powerful platform that scales with your team’s needs.

References

Author

Ricky Gummadi

Posted on

2025-05-01

Updated on

2025-07-07

Licensed under

Comments