Comments (6)
I would like to offer another point of view, not because im sure its correct (im not), but in order to enrich the discussion 😄
I like to look at devcontainers as a result of a "configuration evolution" if you will.
So we begin with a custom docker file and some configuration line. looks something like this:
{
"build": { ".Dockerfile" },
"postCreateCommand": "./configure poetry"
}
and the dockerfile is something like this:
FROM ubuntu:22.04
install python
install poetry
This had the problem of rewriting or copy pasting the same dockerfile to every other project or writing it from scratch. Almost no code share
Enter custom images python-with-poetry
python-with-cpp
node-with-yarn
node-with-pnpm
, variants on these images already exists and already flooding docker hub. I know I personally also built something similar to my project, that was to avoid the build time but It was a mistake. These suffer the issues of:
- Tend to be bloated, in fear of missing some important component so usually they end up drifting towards being catch-all universal image
- They tend to be outdated fast, this is because they stuck in whatever base image they started from forever (ubuntu:18 for example)
- The images are indeed communal, but the layers on the dockerfile which originate it arent. each docker image author craft his own docker layers and install things differently, so no community benefit there.
I feel at this point, devcontainer "features" is the next evolutionary step, in the sense the solve all of the above problems. lets see how:
- Feature selection happens dynamicly means you choose what you want when you need it, no need for bloat.
- Features are decoupled from the base image, thus enabling you to update uubntu18 to ubuntu22 for example without changing anything else.
- Lastly - features can be seen as common and sharable docker layers, with the additional benefit of having a standard interface. no longer copy pasting docker layers from some online example/stackoverflow.
So I feel we are going a step backwards in a sense, giving up on these benefits when using custom image like ghcr.io/devcontainers-contrib/images/python-with-poetry
.
optimal might be something like
{
"image": "debian",
"features": {
"python": {},
"poetry": {},
"postCreateCommand": "./configure && poetry install"
}
I concede poetry as a feature is itself a decision that I have to defend. As you said it can easily be seen as parameter to the python feature, or a postCreateCommand
line. I guess this should be seen as case to case basis
The easiest justification for a feature is if its installation is non trivial, yet common enough that its worth packaging it in a sharable way. Another reason might be that it has installation gotchas that you want to help people avoid. But again thats easy
Poetry, as an example, falls in that category exactly because it offers some more intricate installation/configuration (the plugins) so you might add it to its interface and save everybody's time.
( btw, In the future I think each company would probably maintain its own product features. so Poetry for example has a for-profit company behind it so they should maintain the feature for their product and update it wit new flags and installation params etc. This should scale the best)
There is another category of feature reasoning - availability to a non-experienced user in that ecosystem
So for example say you are js developer and you need some python command line tool ( for example cookiecutter. ansible. etc). You dont feel comfortable enough with python to install and manage it and install a package etc, so its easier for you to use a feature for that. Or say a python developer who is not comfortable with the javascript ecosystem, but want to use fkill
or heck devcontainer cli
. Yes, this is much weaker argument, but still worth mentioning
Regarding templates, I fully agree about 1 to 1 relation between template and custom images now that I think of it. And because templates have well defined structure they are so much more customizable / maintainable / explanatory they are preferable. maybe they an supersede dockerfiles as a means to build production images as well? one can dream 😛
from .github.
In my opinion, this seems like it should have the following devcontainer.json (reasoning below)
{
"image": "ghcr.io/devcontainers-contrib/images/python-with-poetry",
"features": {
"ghcr.io/devcontainers/features/node": {}
},
"postCreateCommand": "./configure && poetry install"
}
The real key here is the...
- The use of a python-with-poetry image
- Lack of options passed to the
node
feature - The use of an autotools-like
./configure
script1 to do magic ✨ (see below for contents) - The
poetry install
being in the postCreateCommand, NOT in./configure
1. The use of a python-with-poetry image
The reason I'm using an example (non-existant as of yet) python-with-poetry image is to demonstrate that images should be designed to meet the requirements of what a typical developer's normal "devcomputer" (their typical working computer) has installed on it for their workflow. In this case, a Python-with-poetry dev can be expected to have the assorted Python, pip, poetry, GNU core utils, etc. all installed.
Here are some images that don't exist now (not in the official devcontainers/images repo) but that I think should exist to fit other workflows:
python-with-poetry
python-with-cpp
node-with-yarn
node-with-pnpm
cpp-with-conan
cpp-with-vcpkg
ubuntu-with-homebrew
haskell
java-with-maven
Notice how all of these images are like typical dev environments? There are certain sub-images within scopes; like Node.js has plain old Node.js, node+yarn and node+pnpm. Same with Python. Python has plain Python, Python+Poetry, and maybe Python+cpp for developing native Python extensions (I'm no expert, I just wanted another Python offshoot example).
I am not a Python expert. Poetry might be standard enough to put in the main Python image! I'm not involved enough to know what's popular and hip. Advice wanted.
2. Lack of options passed to the node
feature
Now lets address what I think makes a feature a feature. Features should act almost exactly like images. They should have a minimal amount of configuration options; likely just versions, similar to how images work:
mcr.microsoft.com/devcontainers/python:3 (latest)
mcr.microsoft.com/devcontainers/python:3.7 (or 3.7-bullseye, 3.7-buster to pin to an OS version)
mcr.microsoft.com/devcontainers/python:3.8 (or 3.8-bullseye, 3.8-buster to pin to an OS version)
mcr.microsoft.com/devcontainers/python:3.9 (or 3.9-bullseye, 3.9-buster to pin to an OS version)
mcr.microsoft.com/devcontainers/python:3.10 (or 3.10-bullseye, 3.10-buster to pin to an OS version)
mcr.microsoft.com/devcontainers/python:3.11 (or 3.11-bullseye, 3.11-buster to pin to an OS version)
(https://github.com/devcontainers/images/tree/main/src/python)
Each of these features should represent the same concept of a "toolchain" that a root image does. For instance, in this example from the Discussion, a Python developer needs to use the Node.js toolchain. So what do they do? They add a Node.js feature to their basic Python image since there is no python-with-nodejs image for a pre-configured devcontainer yet.
If an image repeatedly sees the same feature used with it over and over, that's the sign a new sub-image should be created! For instance, if this Python & Node.js dev environment became a new trend it should get a python-with-node
image made to make it easier!
Now back to the "Lack of options passed to the node
feature" thing... These features should act like "default toolchains" as though someone just happened to have two of them installed. Basically what I'm saying is that features should act like the contents of the Dockerfile were pulled into an install.sh
script instead of being tied to a container. The following should be identical with no extra options in the feature that aren't present on the default image.
{
"image": "mcr.microsoft.com/devcontainers/python"
} |
{
"image": "mcr.microsoft.com/devcontainers/base",
"features": {
"ghcr.io/devcontainers/features/python": {}
}
} |
Adding an option like one described in https://github.com/devcontainers-contrib/features/discussions/202 would break that contract and add things to features that aren't present by default and critically cannot be expected to be that way on an average developer's laptop. Instead of being in an easy-to-see shell script, these sudo poetry self add poethepoet[poetry_plugin]
commands are now stuffed in an obscure devcontainer feature image that a non-devcontainer user isn't going to know what (below) means!
{
"features": {
"ghcr.io/devcontainers-contrib/poetry": {
// You'll have to repeat these instructions in the readme to make sure
// that non-devcontainers users do this too!
"plugins": "poethepoet[poetry_plugin]"
}
}
}
Compare that to a common centralized setup.sh or postCreateCommand that handles these non-default things! That leads into...
3. The use of an autotools-like ./configure
script1
This is where we do the addition of poethepoet task runner and install prettier.
#!/bin/bash -e
npm i -g prettier
poetry self add poethepoet[poetry_plugin]
The biggest pro of this method over using feature options is that users who don't use devcontainers who have the needed Node.js and Python stuff installed can just run ./configure
and it will do it. If instead you put those options into the devcontainer.json, now those people who don't use devcontainers (like your CircleCI service!) are fresh out of luck and you now need to write those shell commands anyways. You might as well use that configure script to do double duty.
Thus this:
- De-deuplicates specifying poetry plugins in the devcontainer.json and in a CI configure script
- Allows non-devcontainers users an easier onboarding experience for non-standard customizations (like global pip or npm modules and custom poetry plugins)
- Explicitly allows those non-devcontainers users to see exactly what they need to undo after they're done with the project (
npm uninstall -g prettier
etc.)
4. The poetry install
being in the postCreateCommand, NOT in ./configure
Honestly, this was just a stylistic choice because poetry has a CI mode. Thus, in CI it's easy to run the (below) to de-couple the poetry installation from the ./configure setup.
- run: ./configure
- run: poetry install --without dev
If ./configure
had done poetry install
, then it would have downloaded those dev deps that we don't need. npm has a similar thing with npm install
vs npm ci
which determines if it install dev deps or not. Ideally, those should be separate; maybe even excluded from that postCreateCommand altogether, depending on your purism!
Anyways, that's my opinion on the solution and architecture to this issue. I am completely open to counter-arguments. I may shift my opinion yet again. That's what's great about opinions: they can change! Thoughts? Comments? Counter-arguments?
/cc @danielbraun89 @DaneWeber (if you're still interested; tell me if you want me to stop pinging you about these things) and @imaxerik
After posting this I realized I missed addressing the templates! 🤦♂️
I think templates (the pre-configured devcontainers.json like terraform-basic) should basically be 1-to-1 with images. For instance, Node.js has an image with the "devcomputer" of a Node.js dev, but the devcontainer.json template gives you the addition of VS Code extensions auto-installing in Codespaces, extra customization for VS Code settings, a postCreateCommand of npm install
, and more! The point is that that's still in the same "Node.js typical workstation setup" sort of idea, but it's extended to include the IDE config now too. Then, after the template gives you a devcontainer.json, you can edit it, add to it, and otherwise mutate it to fit your custom project. The templates are just that. Templates for future additions and configuration.
Footnotes
from .github.
I'm just starting to familiarize myself with the way Dev Containers and features work so I don't have (m)any opinions yet, but I do like what I've read so far in these discussions.
There's one thing that stood out to me, though:
If an image repeatedly sees the same feature used with it over and over, that's the sign a new sub-image should be created! For instance, if this Python & Node.js dev environment became a new trend it should get a
python-with-node
image made to make it easier!
I'd use caution with that, and maybe recommend a depth limit, lest sub-image names start giving isekai web novel titles a run for their character counts. For example, both python-with-poetry
and python-with-pipenv
(pipenv is similar to poetry) would likely be useful, but python-with-poetry-and-black
and python-with-pipenv-and-black
might be straying into silly territory (black is a Python code formatter). Some commonly used features may be better off as features that are easily composed together rather than being baked into images.
from .github.
/re @imaxerik
For example, both python-with-poetry and python-with-pipenv (pipenv is similar to poetry) would likely be useful, but python-with-poetry-and-black and python-with-pipenv-and-black might be straying into silly territory (black is a Python code formatter).
Good points. I'd say it's kinda discretionary. Features are like "add-ons". If an addon becomes so popular, it might good to bake it in to your product/thing. I do agree that the names could get silly. At some point, yeah you'd have to cut it off.
Since there's only likely to be maybe 2-5 truly "mainstream" configs, I don't think this is too much of an issue.
Also quick question about black: it seems kinda like it should be what I (from the npm world) think of as a dev dependency that is listed in the package.json, kinda like prettier is for nodejs projects. Does python have a mechanism to that too, or are dev-only packages installed globally?
"scripts": {
"lint": "prettier -w ."
},
"devDependencies": {
"prettier": "latest"
}
Some quick googling suggests that poetry has dependency groups but I'm wondering if that's actually used in the real world. i.e. have you seen it?
from .github.
/re @danielbraun89
I do agree with some of your sentiments! Thanks for providing another opinion. It's nice to have someone else too bounce ideas off of.
In your arguments to use features instead of images, you mention that "Feature selection happens dynamicly means you choose what you want when you need it, no need for bloat."
I think this can lead to a different kind of bloat: build-time bloat. For instance, the mcr.microsoft.com/devcontainers/universal
image is lightning fast. If you start a Codespace on a random repo, it's ready in 15 seconds flat.
Compare that to a .devcontainers/devcontainer.json that looks like this:
{
"image": "mcr.microsoft.com/devcontainers/base",
"features": {
"ghcr.io/devcontainers/features/python": {},
"ghcr.io/devcontainers/features/node": {},
"ghcr.io/devcontainers/features/java": {},
// ...
}
}
Each time you start a new devcontainer, it takes literally 10 minutes to get it built. It has become a Dockerfile. Sure, you can cache it, but that is, in essence just turning a devcontainer.json into a container Docker image, just like a Dockerfile would.
What I'm getting at is that Docker images in the image
key can be cached and pre-made to make them faster. Features are add-ons that are dynamically executed as your devcontainer comes online. In my brain 🧠 they're sort of like postCreateCommand
, but on steroids and in install.sh
script.
TL;DR: Features (in my experience) lead to poor dev onboarding experience from long build times. Images provide better, faster, cached common environments. Features should be for addons, images should be for mainline dev presets.
There is another category of feature reasoning - availability to a non-experienced user in that ecosystem
So for example say you are js developer and you need some python command line tool ( for example cookiecutter. ansible. etc). You dont feel comfortable enough with python to install and manage it and install a package etc, so its easier for you to use a feature for that. Or say a python developer who is not comfortable with the javascript ecosystem, but want to use fkill or heck devcontainer cli. Yes, this is much weaker argument, but still worth mentioning
I actually do agree that this is a sore spot. It is difficult to incorporate things you know nothing about. devcontainer features could become a "common denominator", but I personally find the idea of maintaining so many features an impossible task. It would also create a "should I use the pre-made feature, or add it to my requirements.txt
file?" dilemma for users. Obviously, if you're in a Python project you should desire to put your deps in a requirements.txt
file so that others can install the project, but "it's easier just to add it to the devcontainer features" will likely prove persuasive.
TL;DR: I agree that for some things like black
or other super common global tools (maybe prettier
or tsc
too, idk) there is an argument to be made to put them in features. I just also think that it pulls those dev dependencies out of the normal flow of the package.json or requirements.txt and puts them somewhere where they shouldn't be: globally.
- Tend to be bloated, in fear of missing some important component so usually they end up difting towards being catch-all universal image
- They tend to be outdated fast, this is because they stuck in whatever base image they started from forever (ubuntu:18 for example)
- The images are indeed communal, but the layers on the dockerfile which originate it arent. each docker image author craft his own docker layers and install things differently, so no community benefit there.
I agree that these are tough issues.
Ideally, all three of these issues could be solved with good governance, similar to how the official devcontainer images work right now: You need a very good reason to add one, and a very good reason to change stuff about it.
More specifically:
- Good governance from people like you who see the evils of a "universal" container could stem the "add one more feature" (hopefully)
- I don't know exactly how to solve this one, but probably regular dependabot pings? Again, good maintainers and governance would help, but yeah this is a tough problem. You're literally fighting time.
- Ideally the installation method wouldn't matter. I know there's some discussion in https://github.com/devcontainers-contrib/features/discussions/173 about asdf as a mainline dep manager, and how features could be installed from multiple sources... BUT I don't think it matters where the thing was installed from, so long as that when the user logs in to their VS Code instance they can run
deno run my-app.js
orpython3 my-script
and have it work. Basically what I'm saying is that installation doesn't matter, only$PATH
exposure matters.
from .github.
@danielbraun89 I don't know if this is a good idea or not, but we can always create more repositories to hold other "types" of features. For instance, there seems to be a clear-ish divide between features that are "ecosystem" or "expected" (common on devcomputers) and those that are npm i -g @aws-amplify/cli
in disguise. We can always make another repo to put a more concrete divide or separation between such features:
- devcontainers-contrib/features
- devcontainers-contrib/ez-features
- devcontainers-contrib/ecosystem-images
- devcontainers-contrib/ecosystem-features
- devcontainers-contrib/npm-features
- devcontainers-contrib/github-features
- devcontainers-contrib/popular-features
Let me know what you think. I'm not 100% behind this idea, but it's a possibility.
from .github.
Related Issues (7)
- Rename & add description HOT 2
- @jcbhmr initiative HOT 1
- [discussion:general] What should go in postCreateCommand, postStartCommand, and postAttachCommand? HOT 2
- Enable organization discussions HOT 3
- [discussion:idea] Create an awesome repo HOT 2
- [discussion:idea] Add workflow template for deploying to GitHub wiki HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from .github.