Skip to content

Conversation

@allonhadaya-maven
Copy link

@allonhadaya-maven allonhadaya-maven commented Jul 15, 2025

This commit updates the default config.containers.shell.entrypoint to execute the startup command with exec rather than as a shell call. This allows Gitlab CI runnners to properly send scripts to CI jobs running the shell container image.

For reference, here's how Gitlab runners bring up a container and run the script:

1.The runner starts a Docker container using the defined entrypoint. The
default from Dockerfile that may be overridden in the .gitlab-ci.yml file.

...

  1. The runner sends the script to the container’s shell stdin and receives
    the output.

This pattern of integrating with Gitlab CI produces improved latency over the current recommended pattern:

  • pipelines build the shell once and then reuse the image in each shell CI job

  • pipelines can skip unnecessary build jobs using CI rules

  • shell CI jobs only load the shell image, not the large devenv image

    update: shout out to @domenkozar for optimizing the devenv image size ee0fd5c 🚀

  • shell CI jobs run script commands as soon as the job container is ready

Users can implement this pattern as follows:

# devenv.nix

{ pkgs, ... }:
{
  env.GREET = "hello";
  packages = [ pkgs.jq ];
  containers.shell = {
    name = "hello-world";
    registry = "docker://my-registry.com/";
  };
}
# .gitlab-ci.yaml

# Build the shell container image and copy it to my-registry.com/hello-world
# Note: defining change rules allows users to skip unnecessary builds

build-devenv-shell:
  stage: build
  image: ghcr.io/cachix/devenv/devenv:v1.6
  script:
    - devenv container
      --copy-args="--dest-creds my-registry-user:$MY_REGISTRY_TOKEN"
      --option containers.shell.version:string "$CI_COMMIT_BRANCH"
      copy shell
  rules:
    - changes:
      - devenv.nix
      - devenv.lock

.devenv-shell:
  image: my-registry.com/hello-world:$CI_COMMIT_BRANCH
  needs:
    - job: build-devenv-shell
      optional: true

# Run CI workloads using the shell container image
# Note: script runs as soon as the job container is ready

greet:
  stage: test
  extends: .devenv-shell
  script:
    - echo $GREET

jq-version:
  stage: test
  script:
    - jq --version

This commit updates the default `config.containers.shell.entrypoint` to execute
the startup command with exec rather than as a shell call. This allows Gitlab
CI runnners to properly send scripts to CI jobs running the shell container
image.

For [reference][gitlab], here's how Gitlab runners bring up a container and run
the script:

> 1.The runner starts a Docker container using the defined entrypoint. The
> default from Dockerfile that may be overridden in the .gitlab-ci.yml file.
>
> ...
>
> 4. The runner sends the script to the container’s shell stdin and receives
> the output.

---

This pattern of integrating with Gitlab CI produces improved latency over the
[current recommended pattern][devenv-ci]:
- pipelines build the shell once and then reuse the image in each shell CI job
- pipelines can skip unnecessary build jobs using CI rules
- shell CI jobs only load the shell image, not the large [devenv image][devenv-image]
- shell CI jobs run script commands as soon as the job container is ready

Users can implement this pattern as follows:

```nix
# devenv.nix

{ pkgs, ... }:
{
  env.GREET = "hello";

  packages = [ pkgs.jq ];

  containers.shell.name = "hello-world";
}
```

```yaml
# .gitlab-ci.yaml

# Build the shell container image and copy it to <registry>/hello-world
# Note: defining change rules allows users to skip unnecessary builds

build:
  stage: build
  image: ghcr.io/cachix/devenv/devenv:v1.6
  script:
    - devenv container copy shell
  rules:
    - changes:
      - devenv.nix
      - devenv.lock

.devenv-shell:
  image: <registry>/hello-world
  needs:
    - build

# Run CI workloads using the shell container image
# Note: script runs as soon as the job container is ready

greet:
  stage: test
  extends: .devenv-shell
  image: <registry>/hello-world
  script:
    - echo $GREET

jq-version:
  stage: test
  image: <registry>/hello-world
  script:
    - jq --version
```

[gitlab]: https://docs.gitlab.com/ci/docker/using_docker_images/#override-the-entrypoint-of-an-image
[devenv-ci]: https://github.com/cachix/devenv/blob/v1.6.1/docs/integrations/devenv-container.md?plain=1#L14-L20
[devenv-image]: https://github.com/cachix/devenv/pkgs/container/devenv%2Fdevenv/402654685?tag=v1.6
@domenkozar
Copy link
Member

See ac2e743 for why bash was used /cc @mcdonc

@allonhadaya-maven
Copy link
Author

allonhadaya-maven commented Jul 15, 2025

See ac2e743 for why bash was used /cc @mcdonc

Ah, thanks for providing that context!

This will represent a breaking change for users who currently have startup commands defined like so:

# No longer supported:
{...}:
{
  containers.shell.startupCommand = "if [ 1 = 1 ]; then echo yup; fi";
}

Users who wish to use shell builtins in their startup command should call the shell explicitly:

# Do this instead:
{...}:
{
  containers.shell.startupCommand = "bash -c 'if [ 1 = 1 ]; then echo yup; fi'";
}

I'd recommend moving forward with this breaking change since moving back to exec enables container runtimes to properly supervise the resulting containerized process. See Best practices ENTRYPOINT:

For example, the Postgres Official Image uses the following script as its ENTRYPOINT:

(code block omitted)

This script uses the exec Bash command so that the final running application becomes the container's PID 1. This allows the application to receive any Unix signals sent to the container.

@domenkozar
Copy link
Member

Could we: exec bash -c $cmd?

@domenkozar
Copy link
Member

@allonhadaya-maven just wondering if that breaks semantics, otherwise I'm +1 for this change!

@allonhadaya-maven
Copy link
Author

@allonhadaya-maven just wondering if that breaks semantics, otherwise I'm +1 for this change!

@domenkozar that might work! Towards the end of this week I'll have a chance to test that entrypoint configuration and also follow up with those documentation updates we discussed.

@domenkozar
Copy link
Member

ping :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants