devcontainers-contrib / .github

2 stars 3 forks source link

[discussion:general] What should go in postCreateCommand, postStartCommand, and postAttachCommand? #5

Closed jcbhmr closed 1 year ago

jcbhmr commented 1 year ago

This should be a discussion. Once discussions are enabled, @danielbraun89 could you move this to the org-level discussions? #3

Originally from https://github.com/devcontainers-contrib/features/discussions/202#discussioncomment-4439676

TL;DR: I'm (@jcbhmr) no longer sure that poetry install should even be in the postCreateCommand spot. Maybe it belongs in something a little closer to the user like postStartCommand and maybe even postAttachCommand so that it's run multiple times.

devcontainers have multiple different lifecycle scripts. Here's a summary:

  1. initializeCommand: A command string or list of command arguments to run on the host machine before the container is created.
  2. onCreateCommand: finalizes container setup when a dev container is created. It and subsequent commands execute inside the container immediately after it has started for the first time. not typically have access to user-scoped assets or secrets
  3. updateContentCommand: executes inside the container. execute at least once, but cloud services will also periodically execute the command to refresh cached or prebuilt containers. no user files or secrets.
  4. postCreateCommand: once the dev container has been assigned to a user for the first time. can use this command to take advantage of user specific secrets and permissions. has local file access.
  5. postStartCommand: run each time the container is successfully started. potentially multiple times per devcontainer over the course of days.
  6. postAttachCommand: A command to run each time a tool has successfully attached to the container.

Open questions:

  1. What should go in postCreateCommand? Just ./configure? ./configure && poetry install?
  2. What (if anything) should go in postStartCommand? npm install? A dev preview HTTP server? A test watcher? Nothing?
  3. Should anything go in postAttachCommand? Maybe a dev HTTP server? Test watcher?

My (@jcbhmr's) opinion is posted as a comment. Looking for more opinions!

/cc @imaxerik since I originally posted this on your thread then moved it here

jcbhmr commented 1 year ago

My personal mental-model of what the use-case for each of these is described below. I don't actually know if this is the right idea or not. There's conflicting advice and pre-existing stuff all over examples.

  1. initializeCommand: I honestly have no idea. Telemetry maybe?
  2. onCreateCommand: No idea again. Haven't ever seen this one used. It seems like anything that goes here should be in the Dockerfile.
  3. updateContentCommand: Since it's periodically executed by cloud services (like Codespaces), I think this is the perfect spot for a pip install --upgrade pip or something similar to keep tooling updated behind-the-scenes without requiring a container re-build.
  4. postCreateCommand: This is where I think the ./configure scripts go. The things that do things once. It can access files when the onCreateCommand can't, so it fits!
  5. postStartCommand: This seems like the place for a pip install -r requirements.txt or an npm install. You want this command to happen when a user starts or re-starts the container. It should happen multiple times over the course of days (each time you re-boot your devcontainer) to keep your deps up-to-date (if using ^1.2.3 or ~=4.5.6 versions).
  6. postAttachCommand: The most likely case for this is an auto-start for a dev script. Something like npm start to start a dev server, or running tools/dev.sh to start an HTTP preview. That's something that should stop when the user disconnects (but doesn't stop the container) and re-start as soon as they connect. Maybe also a cat GREET.txt or something to show a message in the terminal about getting started!

The reason I don't think the poetry install or whatever should be in postCreateCommand is from this article:

Option 1: Install requirements.txt dependencies using devcontainer.json properties like postCreateCommand and postStartCommand.

The downside of the "postCreateCommand" property from Option 1 is that an updated requirements.txt requires a container rebuild. This can become a time sink if you're updating the file frequently.

We can avoid container rebuilds altogether if we use the "postStartCommand" property from Option 1.

The "postStartCommand" command is possible because containers have a running session in which developers interacting with a running dev container can exit the container and return to the running session without having to rebuild the image and lose their session. This option takes advantage of the running session. With this command it appears as if we've done enough.

The author focuses specifically on not using the terminal, but re-starting the container instead. I think it'd be far better to just re-run the pip install ... command manually in the terminal.

But, picture this: after leaving your devcontainer to sit saved but not started and you come back to it after 3 days... Either 1 of 2 things happen:

  1. You used postCreateCommand and you need to manually run pip install ... to update your deps to their new patch versions
  2. You used postStartCommand or postAttachCommand and it runs automagically ✨ when you restart the container IDE

There are advantages to both approaches. Having just discovered that these other lifecycle commands exist today, I'm hesitant to form a concrete opinion as of yet.


EDIT: It looks like (most) of these lifecycle commands can be specified by images themselves! They can add metadata in their Dockerfiles to tap into these devcontainer special lifecycle hooks.

Metadata properties marked with a 🏷️️ can be stored in the devcontainer.metadata container image label in addition to devcontainer.json. This label can contain an array of json snippets that will be automatically merged with devcontainer.json contents (if any) when a container is created.

-- https://containers.dev/implementors/json_reference/

jcbhmr commented 1 year ago

💡 I discovered another cool feature today! VS Code (not devcontainers) allows you to run tasks at folderOpen too!

// .vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [{
    "label": "Install Poetry packages",
    "type": "shell",
    "command": "poetry install",
    "runOptions": {
      "runOn": "folderOpen"
    }
  }]
}

https://sdivakarrajesh.medium.com/automating-task-to-run-on-startup-in-vscode-fe30d7f99454

Maybe these user-level commands belong in user-level VS Code specific settings. This would have the advantage of not cluttering a CI environment that uses the devcontainers/ci GitHub Action

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: devcontainers/ci@v0.2
        with:
          # poetry install isn't run in the devcontainer, it's only run when the user
          # uses VS Code and opens the folder! This means CI can do something INSTEAD OF
          # poetry install vs. IN ADDITION TO poetry install. If we used postCreateCommand
          # or postStartCommand, poetry install would already have run 🤷‍♂️.
          runCmd: poetry install --only dev && pytest test/my_test.py

But that's just another perspective/idea! Debate is still open!