Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: reusable containers#636

Open
matthiasschaub wants to merge 25 commits into
testcontainers:main from
GIScience:reusable_containers
Open

feat: reusable containers #636
matthiasschaub wants to merge 25 commits into
testcontainers:main from
GIScience:reusable_containers

Conversation

@matthiasschaub

@matthiasschaub matthiasschaub commented Jul 3, 2024
edited
Loading

Copy link
Copy Markdown

adresses #109

Todo:

Open questions:

  • Should reuse_enable also be configurable via environment variable?
  • Currently to make reuse work ryuk needs to be disabled. Should the user do this manually? -> if so warn the user if with_reuse in use but ryuk is disabled.
  • Current tests are sensitive to users ~/.testcontainers.properties. This file should not be present during test run.
  • Should each container class have the possibility to state if this container can be reused? (DockerContainer.reusable: bool = True)

arian, MichaelsJP, ppmathis, and steve148 reacted with thumbs up emoji
Comment thread index.rst
... second_id == container._container.id
>>> print(first_id == second_id)
True

@matthiasschaub matthiasschaub Jul 3, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the user be warned that by using this feature, containers need to be removed manually? (That this feature should not be used in a CI)

Also, do we need to make clear how this feature works (explaining the hash in use). -> If a container's run configuration changes, the hash changes and a new container will be used.

@alexanderankin alexanderankin Jul 31, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like you have added these comments to the doc, i think that is fine. the hash would be great to add as users would benefit from knowing exactly what is hashed.

  • self.image,
  • self._command,
  • self.env,
  • self.ports,
  • self._name,
  • self.volumes,
  • str(tuple(sorted(self._kwargs.items()))), - this may fail and why i want to have this be tucked away inside an obviously readable if block

@matthiasschaub matthiasschaub Apr 22, 2025
edited
Loading

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: Maybe use something like https://github.com/knowsuchagency/picocache/blob/main/picocache/utils.py#L9 for making the hash key

@matthiasschaub matthiasschaub Dec 18, 2025
edited
Loading

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before running str(tuple(sorted(self._kwargs.items()))), self._kwargs is now beeing checked for None -> 3894eb1

Comment thread core/testcontainers/core/config.py Outdated

codecov Bot commented Jul 8, 2024
edited
Loading

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 93.33333% with 3 lines in your changes missing coverage. Please review.

Please upload report for BASE (main@0ce4fec). Learn more about missing BASE report.

Files Patch % Lines
core/testcontainers/core/config.py 80.00% 2 Missing ⚠️
core/testcontainers/core/container.py 96.55% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@
## main #636 +/- ##
=======================================
 Coverage ? 77.27% 
=======================================
 Files ? 11 
 Lines ? 616 
 Branches ? 93 
=======================================
 Hits ? 476 
 Misses ? 113 
 Partials ? 27 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown
Member

will review and merge if you have no other plans for changes

Copy link
Copy Markdown
Author

@alexanderankin we would welcome a review from you, thanks. They are no further plans right now except addressing what ever comes up in the code review.

Comment thread core/testcontainers/core/container.py Outdated
Comment on lines +99 to +131
args = (
self.image,
self._command,
self.env,
self.ports,
self._name,
self.volumes,
str(tuple(sorted(self._kwargs.items()))),
)
hash_ = hashlib.sha256(bytes(str(args), encoding="utf-8")).hexdigest()

if self._reuse and (not c.tc_properties_testcontainers_reuse_enable or not c.ryuk_disabled):
logging.warning(
"Reuse was requested (`with_reuse`) but the environment does not "
+ "support the reuse of containers. To enable container reuse, add "
+ "the 'testcontainers.reuse.enable=true' to "
+ "'~/.testcontainers.properties' and disable ryuk by setting the "
+ "environment variable 'TESTCONTAINERS_RYUK_DISABLED=true'"
)

if self._reuse and c.tc_properties_testcontainers_reuse_enable:
docker_client = self.get_docker_client()
container = docker_client.find_container_by_hash(hash_)
if container:
if container.status != "running":
container.start()
logger.info("Existing container started: %s", container.id)
logger.info("Container is already running: %s", container.id)
self._container = container
else:
self._start(hash_)
else:
self._start(hash_)

@alexanderankin alexanderankin Jul 31, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we refactor this so that is is obvious where the if clause is that triggers this?

want to make sure

  1. we are doing the hash inside the clause
  2. want to make it more readable - not in general but specifically for ensuring correcteness of logic that disables or enables reuse

@matthiasschaub matthiasschaub Aug 2, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! I will revisit this part next week and try to improve upon it.

@matthiasschaub matthiasschaub Aug 3, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexanderankin I moved the generation of the hash inside the if-clause and removed passing the hash_ to start if reuse is not in use. I think that makes it better readable in general.

Comment thread core/testcontainers/core/container.py Outdated
Comment on lines +108 to +157
self._container.remove(force=force, v=delete_volume)
if self._reuse and c.tc_properties_testcontainers_reuse_enable:
self._container.stop()
else:
self._container.remove(force=force, v=delete_volume)

@alexanderankin alexanderankin Jul 31, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, isnt the point to not even stop it so it is warm for next run? i guess if people are using the explicit api then whatever. I do see a bit of a mirror with start so i guess it will just have to be consistent and maybe clear in docs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other languages, having a reusable container does not change the contract of the stop() method. This is obviously something that needs to be considered to make this a full fledged use case, but as of now, I would suggest we start with an experimental reusable implementation, that mirrors the Java implementation.

@matthiasschaub matthiasschaub Aug 2, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see. I updated the code and documentation (how to use reusable containers) to not change the contract of the stop() method.

Comment thread index.rst Outdated
Comment on lines +120 to +125
Reusable Containers (Experimental)
----------------------------------

Containers can be reused across consecutive test runs. To reuse a container, the container configuration must be the same.

Containers that are set up for reuse will not be automatically removed. Thus, those containers need to be removed manually.

@alexanderankin alexanderankin Jul 31, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"...removed manually."

maybe add:
"In re-usable mode, the 'stop' api on a container will now 'stop' a container, rather than 'remove' it"

@matthiasschaub matthiasschaub Aug 2, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this discussion, the stop method has not been changed.

Comment thread index.rst
... second_id == container._container.id
>>> print(first_id == second_id)
True

@alexanderankin alexanderankin Jul 31, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like you have added these comments to the doc, i think that is fine. the hash would be great to add as users would benefit from knowing exactly what is hashed.

  • self.image,
  • self._command,
  • self.env,
  • self.ports,
  • self._name,
  • self.volumes,
  • str(tuple(sorted(self._kwargs.items()))), - this may fail and why i want to have this be tucked away inside an obviously readable if block

Comment thread core/testcontainers/core/container.py Outdated
Comment on lines +133 to +135
if self._network:
self._network.connect(self._container.id, self._network_aliases)
return self

@alexanderankin alexanderankin Jul 31, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently this part is also fairly jank and we should remove/rework so as a note to myself i can only do that after this pr merges

matthiasschaub reacted with thumbs up emoji

kiview commented Aug 1, 2024

Copy link
Copy Markdown
Member

Thanks for looking into this @matthiasschaub 👋
We have no clean spec for this features in Java / across languages, so for now I would suggest, we mostly mirror the Java implementation, including its limitations. We can sync on a cross language spec in the future that provides a better DX, but for now I would strongly favor an implementation that mirrors Java.

I already left some comments within the PR.

Currently to make reuse work ryuk needs to be disabled. Should the user do this manually?

Given the above, if a container has reusable set, it must not be registered with the Ryuk cleanup label (see tc-java).

Comment thread core/testcontainers/core/container.py Outdated
self.volumes,
str(tuple(sorted(self._kwargs.items()))),
)
hash_ = hashlib.sha256(bytes(str(args), encoding="utf-8")).hexdigest()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we use the full container create request as the hash input. In tc-java, this is the CreateContainerCmd from docker-java, I guess we have some equivalent request object from the Docker Python SDK somewhere available?

@matthiasschaub matthiasschaub Aug 3, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that would be the ideal solution. Unfortunately, I could not find an equivalent function to CreateContainerCmd in the Docker SDK for Python: Not by going through the documentation and not by browsing the code base.

@matthiasschaub matthiasschaub Jul 31, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of sh256 its probably cheaper and faster to use zlib.crc32.

@matthiasschaub matthiasschaub Dec 15, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To compute a hash to identify container across runs, crc32 is used instead of
sha256, which is faster while still being fit-for-purpose.

do not create Ryuk cleanup instance if reuse enabled and container has
been start with `with_reuse`

matthiasschaub commented Aug 2, 2024
edited
Loading

Copy link
Copy Markdown
Author

Thanks for looking into this @matthiasschaub 👋 We have no clean spec for this features in Java / across languages, so for now I would suggest, we mostly mirror the Java implementation, including its limitations. We can sync on a cross language spec in the future that provides a better DX, but for now I would strongly favor an implementation that mirrors Java.

I already left some comments within the PR.

Currently to make reuse work ryuk needs to be disabled. Should the user do this manually?

Given the above, if a container has reusable set, it must not be registered with the Ryuk cleanup label (see tc-java).

Thanks for the review @kiview! I agree. It is sensible to follow the Java implementation.

In commit 1ea9ed1 I do not create a Reaper instance during container start-up if reuse is enabled and container has been started with with_reuse. This works as expected as long as no other container is started without with_reuse. In that case a Reaper instances is created which will remove the reuse container as well.
Is there a way to explicitly exclude a container from the Reapers routine?

Copy link
Copy Markdown
Author

Please let me know if I can do anything to advance this PR towards merging :)

gpkc reacted with thumbs up emoji

Comment thread core/testcontainers/core/config.py Outdated
enabled = self.tc_properties.get("testcontainers.reuse.enable")
return enabled == "true"

def timeout(self) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that timeout here had the @property decorator before. Was it removed by accident?

@matthiasschaub matthiasschaub May 27, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was an accident.

@matthiasschaub matthiasschaub May 27, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I restored the @property decorator.

gpkc reacted with thumbs up emoji

Copy link
Copy Markdown

Is this PR still intended to be merged?

alexanderankin, glebignatieff, ppmathis, and rominf reacted with thumbs up emoji gpkc and emjames reacted with eyes emoji

return self.tc_properties.get("tc.host")

def tc_properties_testcontainers_reuse_enable(self) -> bool:
enabled = self.tc_properties.get("testcontainers.reuse.enable")

@matthiasschaub matthiasschaub Dec 15, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirroring the Java implementation, I would like to make this configurable via environment variable as well:

https://java.testcontainers.org/features/reuse/#how-to-use-it

What do you think?

Copy link
Copy Markdown
Author

When reviewing, please also check open questions stated in the PR description. Thanks :)

iirekm commented Jan 2, 2026

Copy link
Copy Markdown

why it takes so long? I would love to have reuse just like in Java

Copy link
Copy Markdown

Upvote

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

Reviewers

@alexanderankin alexanderankin Awaiting requested review from alexanderankin

@kiview kiview Awaiting requested review from kiview

@eddumelendez eddumelendez Awaiting requested review from eddumelendez

@gpkc gpkc Awaiting requested review from gpkc

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /