Getting new team members up to speed quickly can be a significant challenge. This guide will walk you through the process of standardizing your project's development environment using dev containers, inspired by the mission of EurekaLabsAI.
EurekaLabsAI, founded by Andrej Karpathy, is an AI+Education company with a mission to create an AI-native school. Their goal is to provide an ideal learning experience, similar to having a world-class expert like Feynman guiding you through complex subjects. While human experts of this caliber are scarce, recent advancements in generative AI make it possible to create AI Teaching Assistants that can support and scale the learning experience.
By adopting a standardized development environment approach, we can apply similar principles to software development teams. Just as EurekaLabsAI aims to make it easy for anyone to learn anything, our goal is to make it easy for any developer to contribute to your project, regardless of their experience level or local setup.
Note: While this guide demonstrates setting up a dev container locally, you can also use this configuration with local VS Code or Daytona. Using Daytona, in particular, offers additional benefits for managing development environments across your team or student group.
Benefits of Standardizing Your Development Environment
Before we dive into the technical details, let's explore why standardizing your development environment is crucial:
Faster onboarding: New team members can start contributing to the project within minutes, not days or weeks. This is particularly important for projects, where students need to quickly set up an environment to start learning.
Consistency across environments: Eliminate the "it works on my machine" problem by ensuring all developers work in identical environments.
Isolated dependencies: Keep project dependencies separate from developers' local systems, preventing conflicts between different projects or versions.
Reproducible builds and tests: Ensure that builds and tests run consistently across all environments, from local development to CI/CD pipelines.
Version-controlled environment: Track changes to your development environment alongside your code, making it easy to roll back or understand why certain decisions were made.
Simplified collaboration: Reduce friction when sharing code or troubleshooting issues, as all team members are working in the same environment.
Easier experimentation: Quickly test new tools or configurations without affecting your local system or other projects.
Step-by-Step Guide to Standardizing Your Project
Now, let's walk through the process of setting up a standardized development environment using dev containers. We'll use Andrej Karpathy's ngram project from EurekaLabsAI as an example throughout this guide. Here is the PR that I have just made on the ngram project with the finalised devcontainer.json file.
Step 1: Set Up Docker
Docker is the foundation for creating dev containers. If you haven't already, you'll need to install Docker on your machine. If you are planing to use remote machine with Daytona you need to install it also on a remote server.
Visit the Docker website and download Docker Desktop for your operating system.
Follow the installation instructions for your OS.
Once installed, open a terminal and run
docker --version
to verify the installation.
Step 2: Fork and Clone the Project
For this example, we'll use the EurekaLabsAI ngram project:
Fork the EurekaLabsAI ngram project on GitHub or any other project you wish to standardize.
Clone your forked repository to your local machine:
1git clone https://github.com/your-username/ngram.git2cd ngram
Step 3: Create a Dev Container Configuration
The dev container configuration defines your development environment. We'll create this configuration in a .devcontainer
folder:
Create a
.devcontainer
folder in your project root:mkdir .devcontainer
Create a
devcontainer.json
file inside the.devcontainer
folder:touch .devcontainer/devcontainer.json
Open
devcontainer.json
in your preferred text editor and add the following basic configuration:
1{2 "name": "Python 3",3 "image": "mcr.microsoft.com/devcontainers/python:3",4 "postCreateCommand": "pip install --user -r requirements.txt",5 "customizations": {6 "vscode": {7 "extensions": [8 "ms-python.python",9 "ms-python.vscode-pylance"10 ]11 }12 }13}14
This configuration uses a pre-built Python 3 image, installs project dependencies, and sets up some useful VS Code extensions for Python development.
Step 4: Customize Your Dev Container
Now, let's customize the dev container to match the specific needs of the ngram project:
Update the devcontainer.json
file to include any additional tools or settings required for the project. For example, you might want to add NumPy as a dependency:
1{2 "name": "Python 3 for ngram",3 "image": "mcr.microsoft.com/devcontainers/python:3",4 "postCreateCommand": "pip install --user -r requirements.txt && pip install numpy",5 "customizations": {6 "vscode": {7 "extensions": [8 "ms-python.python",9 "ms-python.vscode-pylance",10 "ms-toolsai.jupyter"11 ]12 }13 },14 "settings": {15 "python.linting.enabled": true,16 "python.linting.pylintEnabled": true17 }18}
Create a requirements.txt
file in the project root if it doesn't already exist, and add any necessary Python packages:
numpy==1.21.0
Step 5: Mount Your Source Code
Ensure your code is accessible within the container by setting up volume mounts:
Add a workspaceMount
and workspaceFolder
to your devcontainer.json
:
1{2 // ... other settings ...3 "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",4 "workspaceFolder": "/workspace"5}6
This configuration mounts your local project directory into the container at /workspace
.
Step 6: Define Container Entrypoints
To automate your environment setup on container start:
Create a post_create.sh
script in the .devcontainer
folder:
1#!/bin/bash2set -e34echo "Installing project dependencies..."5pip install --user -r requirements.txt67echo "Setting up pre-commit hooks..."8pip install pre-commit9pre-commit install1011echo "Environment setup complete!"
Update your devcontainer.json
to use this script:
1{2 // ... other settings ...3 "postCreateCommand": "bash .devcontainer/post_create.sh"4}
Step 7: Test and Refine
Now it's time to test your dev container setup:
If you're using VS Code, install the "Remote - Containers" extension.
Open the command palette (Ctrl+Shift+P or Cmd+Shift+P) and select "Remote-Containers: Reopen in Container".
VS Code will build the container and reopen your project inside it.
Once inside the container, open a terminal and verify that all tools and dependencies are correctly installed:
1python --version2pip list
Try running the ngram project code to ensure everything works as expected.
If you encounter any issues, refine your devcontainer.json
and post_create.sh
script as needed.
Step 8: Share with Your Team or Students
Once your dev container setup is working correctly:
Commit your changes to version control:
1git add .devcontainer requirements.txt2git commit -m "Add dev container configuration"3git push
Update your project's README.md to include instructions for using the dev container:
1## Development Setup23This project uses dev containers to provide a standardized development environment. To get started:451. Install [Docker Desktop](https://www.docker.com/products/docker-desktop) and [Visual Studio Code](https://code.visualstudio.com/).62. Install the "Remote - Containers" extension in VS Code.73. Clone this repository and open it in VS Code.84. When prompted, click "Reopen in Container" or use the command palette to select "Remote-Containers: Reopen in Container".95. Wait for the container to build and initialize. This may take a few minutes the first time.106. You're now ready to start developing!
Create a pull request to merge your changes back into the original project.
Step 9: Create a Workspace with Daytona
For an even more streamlined experience, you can use Daytona to create and manage your development environment:
If you haven't already, install Daytona by following the quick start guide.
Once Daytona is installed and configured, create a new workspace for your project:
daytona create https://github.com/EurekaLabsAI/ngram
Daytona will set up the workspace based on your dev container configuration.
Once the workspace is ready, you can start coding immediately by running:
daytona code
This command will open your preferred IDE (as configured in Daytona, you can do this with daytona ide
) with your project ready to go. All the tools, dependencies, and configurations specified in your dev container will be automatically set up.
Using Daytona ensures that every team member or student can quickly spin up an identical development environment, regardless of their local setup. This approach further streamlines onboarding and collaboration, making it even easier for anyone to contribute to or learn from your project.
Best Practices and Tips
As you work with dev containers, keep these best practices in mind:
Keep it lightweight: Only include tools and dependencies that are absolutely necessary for your project.
Use multi-stage builds: If your project requires a complex build process, consider using multi-stage Docker builds to keep your final image small.
Cache dependencies: Use Docker's caching mechanisms to speed up container builds.
Keep containers ephemeral: Don't store important data in the container itself. Use volumes or bind mounts for persistent data.
Use Docker Compose for complex setups: If your project requires multiple services (e.g., a database), consider using Docker Compose to define and manage them.
Regularly update base images: Periodically rebuild your dev container with updated base images to ensure you have the latest security patches.
Document any manual steps: If there are any steps that can't be automated, clearly document them in your README.
Conclusion
By implementing a standardized development environment using dev containers, you're making your project accessible to developers of all experience levels.
This approach not only simplifies onboarding but also ensures consistency across your team, reduces environment-related issues, and allows developers to focus on what really matters: building great software.
Remember, the goal is to create an environment where developers can dive straight into the code without getting bogged down by setup and configuration issues. With a well-configured dev container, your project will be ready for collaboration, experimentation, and innovation.