Introducing Disposables: Daemonless-ready Testcontainers alternative

By akash_rawal, created: 2024-10-27, last modified: 2024-11-03

In my last article, I wrote about Testcontainers' dependency on Docker socket access. In a nutshell, Testcontainers needs the Docker socket to remove containers after use. I also wrote on a new method to clean up containers that does not rely on Docker socket access.

But how further can we push that method?

Design

The goal, in case it wasn't clear yet, is to write a Testcontainers-like library that works well with Podman. Testcontainers is a library that starts your test dependencies in a container and stop them after you are done using them.

Starting a container is easy, stopping them is the hard part.

Our new library will replace the entrypoint of the container with our own, and use that entrypoint to terminate the container from within. I am calling the new library Disposables.

Given that we have our own entrypoint inside the container, there is a lot more we can do than just terminate the container after use.

The entrypoint can do more

Testcontainers library is written in many different programming languages. All features of Testcontainers need to be re-implemented in each programming language. We however can take advantage of running our own entrypoint inside the container, and add feature implementation in the entrypoint. This way a lot of implementation only needs to be done once, instead of re-implementing them in each programming language.

For example, waiting for containers to become ready is entirely done within the entrypoint, and only readiness notification is sent back to the test suite.

I have some future plans to implement some Testcontainers features, like Testcontainers modules. I think I only have to write the implementation once and only write the API for each programming language, overall I predict 'modules' will be significantly less work to support.

Conclusion

You can try out the library now at https://github.com/akashrawal/disposables. Right now only Java and Rust languages are available, as I am basically only developing based on my use cases.

I have tested Disposables with Docker daemon configured with user namespaces and with SELinux. Where Testcontainers has issues, Disposables just works, no configuration needed.

user@localhost:~/selinux-test$ export DISPOSABLES_ENGINE=docker
user@localhost:~/selinux-test$ cargo test                                                             
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running unittests src/main.rs (target/debug/deps/selinux_test-a03052e3d408ceef)

running 1 test
test test::nginx ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.86s

user@localhost:~/selinux-test$ export DISPOSABLES_ENGINE=podman
user@localhost:~/selinux-test$ cargo test
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running unittests src/main.rs (target/debug/deps/selinux_test-a03052e3d408ceef)

running 1 test
test test::nginx ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.73s

user@localhost:~/selinux-test$ sestatus 
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Memory protection checking:     actual (secure)
Max kernel policy version:      33