{"id":18374563,"url":"https://github.com/massix/purescript-testcontainers","last_synced_at":"2026-02-02T04:35:12.214Z","repository":{"id":212851960,"uuid":"730794641","full_name":"massix/purescript-testcontainers","owner":"massix","description":"Purescript bindings for Testcontainers","archived":false,"fork":false,"pushed_at":"2024-01-23T13:57:13.000Z","size":176,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-16T06:51:28.277Z","etag":null,"topics":["purescript","purs","testcontainers","testcontainers-purescript","testcontainers-purs"],"latest_commit_sha":null,"homepage":"","language":"PureScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/massix.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-12-12T17:29:16.000Z","updated_at":"2024-02-24T05:24:55.000Z","dependencies_parsed_at":"2025-04-11T02:46:24.858Z","dependency_job_id":"aaf4c790-eb0a-4f82-8121-ea867e7dbd95","html_url":"https://github.com/massix/purescript-testcontainers","commit_stats":null,"previous_names":["massix/purescript-testcontainers"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/massix/purescript-testcontainers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massix%2Fpurescript-testcontainers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massix%2Fpurescript-testcontainers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massix%2Fpurescript-testcontainers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massix%2Fpurescript-testcontainers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/massix","download_url":"https://codeload.github.com/massix/purescript-testcontainers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massix%2Fpurescript-testcontainers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29005203,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T04:25:24.522Z","status":"ssl_error","status_checked_at":"2026-02-02T04:24:51.069Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["purescript","purs","testcontainers","testcontainers-purescript","testcontainers-purs"],"created_at":"2024-11-06T00:15:04.858Z","updated_at":"2026-02-02T04:35:12.190Z","avatar_url":"https://github.com/massix.png","language":"PureScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Testcontainers for PureScript\n\n![Tests](https://github.com/massix/purescript-testcontainers/actions/workflows/purescript.yml/badge.svg)\n\n![Testcontainers Logo](https://testcontainers.com/images/testcontainers-logo.svg)\n\n## Table of contents\n\n- [Introduction](#introduction)\n- [Quick example](#quick-example)\n- [Features](#features)\n- [Local Development](#local-development)\n  - [Nix](#nix)\n- [Containers](#containers)\n  - [Create container](#create-container)\n  - [Create a container from a Dockerfile](#create-a-container-from-a-dockerfile)\n  - [Start a container](#start-a-container)\n  - [Stop a container](#stop-a-container)\n  - [Configuring the container](#configuring-the-container)\n    - [Port Mapping](#port-mapping)\n    - [Set a Wait Strategy](#set-a-wait-strategy)\n      - [Configuring the timeout](#configuring-the-timeout)\n      - [ListeningPorts](#listeningports)\n      - [LogOutput](#logoutput)\n      - [HealthCheck](#healthcheck)\n      - [HttpStatusCode](#httpstatuscode)\n      - [HttpResponsePredicate](#httpresponsepredicate)\n      - [ShellCommand](#shellcommand)\n    - [Environment variables](#environment-variables)\n    - [Privileged Mode](#privileged-mode)\n    - [Capabilities](#capabilities)\n    - [Set User](#set-user)\n    - [Set Command](#set-command)\n    - [Exec a command](#exec-a-command)\n    - [Use the withContainer helper](#use-the-withcontainer-helper)\n- [Network](#network)\n  - [Create a network](#create-a-network)\n  - [Start a network](#start-a-network)\n  - [Attach a container to a network](#attach-a-container-to-a-network)\n- [Docker Compose](#docker-compose)\n  - [Create an environment](#create-an-environment)\n  - [Up an environment](#up-an-environment)\n  - [Down an environment](#down-an-environment)\n  - [Use the `withCompose` helper](#use-the-withcompose-helper)\n  - [Get a container from the environment](#get-a-container-from-the-environment)\n  - [Set Wait strategies for containers](#set-wait-strategies-for-containers)\n  - [Use Profiles](#use-profiles)\n  - [Automatically rebuild](#automatically-rebuild)\n  - [Use Environment variables](#use-environment-variables)\n    - [From files](#from-files)\n    - [From Code](#from-code)\n\n## Introduction\n[Testcontainers](https://testcontainers.com/) is an **opensource framework**\nfor providing throwaway, lightweight instances of databases, message brokers,\nweb browsers, or just about **anything that can run in a Docker container**.\n\nNo more need for mocks or complicated environment configurations.\n**Define your test dependencies as code**, then simply run your tests and\ncontainers will be created and then deleted.\n\nWith support for many languages and testing frameworks,\n**all you need is Docker**.\n\n## Quick example\nA lot of examples are in the [tests folder](./test/Test/), but since we all\nlove to see some code from time to time, here is a very quick example on how to\nlaunch an `alpine` container and execute the `ps` command in it, checking the\nresult:\n\n```purescript\nmodule Main where\n\nimport Prelude\nimport Data.Either (Either(..))\nimport Effect (Effect)\nimport Effect.Aff (launchAff_)\nimport Effect.Console as Console\nimport Test.Testcontainers as TC\n\nmain :: Effect Unit\nmain = do\n  launchAff_ $ do\n    let alpineContainer = TC.setCommand [ \"sleep\", \"infinity\" ] $ TC.mkContainer \"alpine\"\n    eitherStarted \u003c- TC.startContainer alpineContainer\n    case eitherStarted of\n      Left err -\u003e Console.logShow err\n      Right started -\u003e do\n        eitherExec \u003c- TC.exec [ \"ps\" ] started\n        case eitherExec of\n          Left err -\u003e Console.logShow err\n          Right { output, exitCode } -\u003e do\n            Console.log $ \"ps output: \" \u003c\u003e output \u003c\u003e \", exitCode: \" \u003c\u003e show exitCode\n\n        void $ TC.stopContainer started\n```\n\nTo avoid some of the `case _ of` a couple of common wrappers are provided. The\ncode above can be rewritten as follows:\n\n```purescript\nmodule Main where\n\nimport Prelude\nimport Data.Either (Either(..))\nimport Effect (Effect)\nimport Effect.Aff (launchAff_)\nimport Effect.Console as Console\nimport Test.Testcontainers as TC\n\nmain :: Effect Unit\nmain = do\n  launchAff_ $ do\n    let alpineContainer = TC.setCommand [ \"sleep\", \"infinity\" ] $ TC.mkContainer \"alpine\"\n    void $ TC.withContainer alpineContainer $ \\started -\u003e do\n      eitherExec \u003c- TC.exec [ \"ps\" ] started\n      case eitherExec of\n        Left err -\u003e Console.logShow err\n        Right { output, exitCode } -\u003e do\n          Console.log $ \"ps output: \" \u003c\u003e output \u003c\u003e \", exitCode: \" \u003c\u003e show exitCode\n```\n\nThis is to be considered as a _low-level_ library, hence it is not making use of\ncomplex monads transformers or anything, it's up to the users to define their own\n`mtl` stack if they need to.\n\nThe library uses `Either String a` as a generic return value for almost all the\noperations, where `a` is the success type, depending on the function called,\nand `String` is to return the errors coming from the underlying FFI interface.\n## Features\nNot all the features of the original Testcontainers library have been\nimplemented yet, I plan to cover 100% of the functionalities but it will take\nsome time to develop everything.\n\nFor now, this is a list of the supported features, more details for each\nfeature are provided further down in the document.\n\n- [X] Creation of containers\n- [X] Basic handling (start/stop) of containers\n- [X] Define wait strategies\n- [X] Start in privileged mode\n- [X] Start with capabilities (enable or disabled)\n- [X] Handle users\n- [X] Launch commands inside of containers\n- [X] Create a network\n- [X] Attach containers to a network\n- [X] Use `docker-compose` definitions\n- [X] Up and down of a compose environment\n- [X] Get containers running in a compose environment\n- [X] Start containers based on a profile\n- [X] Rebuild containers automatically\n- [X] Set environment variables from files\n\n## Local Development\nIf you want to test the library locally, you need to have:\n- the latest version of `spago` and `purs`\n- a running `docker` daemon\n- the `testcontainers` package installed from `npm`\n\nAfter cloning the repository locally, you can run the tests via\n`spago` by issuing the command: `spago test`.\n\n### Nix\nFor NixOS and nix users, a [flake.nix](./flake.nix) is provided, it uses the\n`purescript-overlay` from `thomashoneyman` to install the latest versions of\n`spago` and `purs`. If you use `direnv` you can simply `direnv allow .` to\nstart a local development shell.\n\nYou will still need to install the `testcontainers` package from `npm` in your\nlocal environment on your own. Usually it is just a matter of running `npm\ninstall`\n\n**Warning**: since I only use a GNU/Linux environment, the `flake.nix` is\nconfigured only for `x86_64-linux` architecture. If you work on a different\narchitecture or environment, feel free to modify the `flake.nix` file and send\nme a Pull Request.\n\n## Containers\nThe most basic building block of the library is the `container` entity. It\nallows users to create, start, interact and stop containers. It is most\nprobably the entity you will use the most in your projects and it is the most\ncomplete one for this wrapper. A container is defined with the following\nunion data:\n\n```purescript\ndata TestContainer\n  = StartedTestContainer Image StartedTestContainer\n  | StoppedTestContainer Image StoppedTestContainer\n  | GenericContainer Image GenericContainer\n```\n\nWhere `Image` is a `newtype` for a `String` object, defining a complete\nreference to a docker image (e.g. `redis:latest`).\n\nFor convenience, a `Typeclass` is defined, which allows `String`s to be\nconverted easily and used in place of the `newtype`:\n\n```purescript\nnewtype Image = Image String\n\nclass IsImage c where\n  toImage :: c -\u003e Image\n\ninstance IsImage Image where\n  toImage = identity\n\ninstance IsImage String where\n  toImage = Image\n```\n\nAlthough the constructors for the `TestContainer` data are public, you are\n**strongly advised** to refrain from using them directly and instead use the\nprovided _smart constructor_ `mkContainer`, which has the following signature:\n\n```purescript\nmkContainer :: ∀ a. IsImage a =\u003e a -\u003e TestContainer\n```\n\nThe reason why, is because the underlying definition uses some `foreign data`\nwhich represent *stateful* JavaScript objects used by the `Testcontainers`\nlibrary directly. All the intricacies of the original library are handled\n\"transparently\" by the wrapper.\n\n### Create container\nAs stated above, creating a container is as simple as calling the `mkContainer`\nfunction, providing a valid definition of a docker image. The definition can\neither be a local image or a remote one, it will use the docker daemon to solve\nthe reference, so by default (and in most standard installations of docker) it\nwill search for the image on `docker.io` hub.\n\n#### Example\n```purescript\n-- Create a container for postgresql, version 14 and the alpine variant\nmkContainer (Image \"postgres:14-alpine\")\n\n-- Since the 'mkContainer' function expects a typeclass IsImage, the same\n-- can be written as follows\nmkContainer \"postgres:14-alpine\"\n```\n\n### Create a container from a Dockerfile\nIt is also possible to create a container from a Dockerfile definition, the system\nwill `docker build` it for you and afterwards you will have a useable container.\n\nThere are actually three different flavours of the same basic functionality:\n1. `mkContainerFromDockerfile` which takes a `FilePath` to a context (i.e., a folder\n    containing a `Dockerfile`) and the name of an image, which will be used as the\n    result of the build;\n1. `mkContainerFromDockerfile'` which takes a `FilePath` to a context, a single\n    `Dockerfile` which **must** be inside of the context and the name of an image,\n    which will be used as the result of the build;\n1. a more complete `mkContainerFromDockerfileOpts` which takes a lot of parameters but\n    gives more control about all the building options, the parameter it takes are:\n    * `FilePath` to a context;\n    * `Maybe String` of a Dockerfile (if `Nothing`, will use `Dockerfile`);\n    * `Maybe PullPolicy` to specify the `PullPolicy` to use;\n    * `IsImage` for the image name;\n    * `Maybe (Array KV)` for the build arguments;\n    * `Maybe Boolean` which, if `Just true` will reuse the cache\n\n#### Example\n\n```purescript\nmain :: Aff Unit\nmain = do\n  cnt \u003c- mkContainerFromDockerfile \"./path/to/a/folder\" \"my-image:latest\"\n```\n\nThe first parameter of the `mkContainerFromDockerfile` function is a `FilePath` to a\nfolder that contains a `Dockerfile` (a build context). The second parameter is the\nfinal name of the image which will be built.\n\nAs an alternative, you can use `mkContainerFromDockerfile'` which accepts a third\nparameter: the name of a `Dockerfile` to use to build the image.\n\n```purescript\nmain :: Aff Unit\nmain = do\n  cnt \u003c- mkContainerFromDockerfile' \"./path/to/a/folder\" \"Dockerfile\" \"my-image:latest\"\n```\n\nFor the third version of the function, it is better to take a look at the\n[test file directly](./test/Test/Images.purs), which contains a full working call of the function.\n\n### Start a container\nOnce you have your container, you can start it by calling the `startContainer`\nfunction, for which the signature is the following:\n```purescript\nstartContainer :: ∀ m. MonadAff m =\u003e TestContainer -\u003e m (Either String TestContainer)\n```\n\nThis function has some **side effects**, hence it expects to be run inside of\nan asynchronous monad (the `Aff` monad). It will return an `Either` value,\ncontaining either an error message - already converted to a string - or a\n`StartedTestContainer` value.\n\n#### Example\nThis is the most basic example of starting a container inside of the `Aff`\nmonad itself.\n```purescript\ntestContainer :: Aff Unit\ntestContainer = do\n  started \u003c- startContainer $ mkContainer \"redis:latest\"\n  -- do something with the container\n  void $ stopContainer started\n```\n\nYou can also use the `Effect` monad by launching an `Aff` computation inside:\n```purescript\ntestContainer :: Effect Unit\ntestContainer = launchAff_ $\n    void $ stopContainer (startContainer $ mkContainer \"redis:latest\")\n```\n\n\n### Stop a container\nSimilar to while starting a container, you can stop it by calling the\n`stopContainer` function, which has the following signature:\n```purescript\nstopContainer :: ∀ m. MonadAff m =\u003e TestContainer -\u003e m (Either String TestContainer)\n```\n\nJust like the `startContainer` function, this function has **side-effects**,\nhence it expects to be run inside of an asynchronous monad.\n\n### Configuring the container\nFor configuring the containers I wanted to provide a similar experience to what\nis provided by the original library, for this reason all the functions which\ninteract with the setup of the container share the same signature:\n\n```purescript\nconfigureSomething :: SomeParameter -\u003e TestContainer -\u003e TestContainer\n```\n\nThis will allow you to take advantage of the __partial application__, one of the\nmain advantages of curried functions in functional programming and compose all\nthe configuration functions together, for example:\n\n```purescript\nconfigureContainer :: TestContainer -\u003e TestContainer\nconfigureContainer =\n  setEnvironment env\n    \u003c\u003c\u003c setUser \"root\"\n    \u003c\u003c\u003c setThis \"that\" \"andThat\"\n    \u003c\u003c\u003c setWaitStrategy [ SomeWaitStrategy ]\n\nmain :: Effect Unit\nmain =\n  let\n    container = configureContainer $ mkContainer \"redis:latest\"\n  in do\n    startContainer container\n    --- etc\n```\n\n#### Port Mapping\nMost of the times, when you want to test something with docker is a service\nwhich will be exposed via some standard TCP or UDP ports. The most basic\nconfiguration function is then to `map` those ports to a localhost port, in\norder to be able to access the exposed service from the host machine.\n\nThe signature of the function is:\n```purescript\nsetExposedPorts :: Array Int -\u003e TestContainer -\u003e TestContainer\n```\n\n**Warning**: by default, `Testcontainers` will not expose the same port to the\nhost, this is to make it possible to run multiple tests in parallel, once you\nhave set the `exposedPorts` you will need to retrieve the real ports exposed\nusing one of the `getMappedPort` functions:\n```purescript\ngetMappedPort :: ∀ m. MonadEffect m =\u003e Int -\u003e TestContainer -\u003e m (Either String Int)\ngetFirstMappedPort :: ∀ m. MonadEffect m =\u003e TestContainer -\u003e m (Either String Int)\n```\n\nThese functions only work on **started containers**.\n\n##### Example\n```purescript\ntestRedis :: Aff Unit\ntestRedis = do\n  started \u003c- startContainer $ setExposedPorts [ 6379 ] $ mkContainer \"redis:latest\"\n  exposedPort \u003c- liftEffect $ getMappedPort 6379 started\n  -- now do something with the exposed port\n```\n\nWhen a container only exposes a single port, as per the example above, you can\nuse the `getFirstMappedPort` function:\n```purescript\ntestRedis :: Aff Unit\ntestRedis = do\n  started \u003c- startContainer $ setExposedPorts [ 6379 ] $ mkContainer \"redis:latest\"\n  exposedPort \u003c- liftEffect $ getFirstMappedPort started\n  -- now do something with the exposed port\n```\n\n\n#### Set a Wait Strategy\nWaiting strategies are a mechanism used internally by `Testcontainers` to know\nwhen a container is to be considered as _available_ for the rest of the code.\n\nWhen setting a Wait Strategy, the library will block the execution of the code\nuntil either the `Timeout` is reached or the Wait Strategy is satisfied.\n\nWait strategies are **always blocking** and you should **never bypass** them,\nsince some functionalities (port forwarding for example) won't be available\nuntil the strategy is satisfied.\n\nThe signature of the function is:\n```purescript\nsetWaitStrategy :: Array WaitStrategy -\u003e TestContainer -\u003e TestContainer\n```\n\nAnd `WaitStrategy` is a union data type that represents the different ways to\nwait, almost all of the strategies defined by the original library are\nimplemented:\n\n```purescript\ndata WaitStrategy\n  = ListeningPorts -- ^ Default waiting strategy, wait for the exposed ports to be available\n  | LogOutput String Int -- ^ Look in the logs for the provided String to appear at least Int times\n  | HealthCheck -- ^ Wait until the health check is healthy\n  | HttpStatusCode String Int Int -- ^ Wait until the Http request at the path String and port Int returns the statuscode Int\n  | HttpResponsePredicate String Int (String -\u003e Boolean) -- ^ Similar to above, but instead of the statuscode, a predicate is required\n  | ShellCommand String -- ^ Run the provided shell command and wait until it returns exit code 0\n```\n\n##### Configuring the timeout\nThe timeout for the wait strategies listed below is configurable using the function\n```purescript\nnewtype StartupTimeout = StartupTimeout Int\nsetStartupTimeout :: StartupTimeout -\u003e TestContainer -\u003e TestContainer\n```\n\nBy default is set to 60 seconds, if the timeout is reached before the\nsuccessful completion of the defined wait strategy then the container is\nimmediately stopped and the `startContainer` function will return with an error\n(i.e. `Left String`)\n\n##### ListeningPorts\nThis is the most basic and default WaitStrategy implemented by\n`Testcontainers`. When you use the `setExposedPorts` function (described\n[here](#port-mapping)) it will wait until the exposed port is available. This\nis done with some heuristics and while it is accurate most of the time,\nsometimes it won't work properly, especially if the container is restarting\ninternally (for example `postgres` containers tend to start the server multiple\ntimes while doing the initial setup). This strategy is always active and\nthere are no configuration parameters to configure it.\n\n##### LogOutput\nThis Wait Strategy will wait for some string to appear in the container's logs\nthe number of times defined by the constructor.\n\nThe provided string will be treated as a **regular expression** by the\nunderlying library.\n\n###### Example\n```purescript\ntestPostgre :: Aff Unit\ntestPostgre = do\n  let config = setExposedPorts [ 5432 ]\n    \u003c\u003c\u003c setWaitStrategy [ LogOutput \"database system is ready to accept connections\" 2 ]\n      -- ^ postgresql will restart after the initial configuration, we know that the\n      -- service is ready only when that line has appeared twice\n  started \u003c- startContainer $ config $ mkContainer \"postgres:14-alpine\"\n  -- do something with postgresql container\n```\n\n##### HealthCheck\nThis Wait Strategy will wait until the health check defined in the `Dockerfile`\nof the image is healthy. In the original library there is a way to define a\ncustom `HealthCheck`, but this has not been implemented in this wrapper yet.\n\n##### HttpStatusCode\nThis Wait strategy will wait for a specific HttpStatusCode to be returned on\nthe given path and at the given port.\n\nThe constructor takes 3 parameters, the first one is the `path`, the second is\nthe `port` and the third is the expected `status code`.\n\n**WARNING**: in order for this wait strategy to work, you have to\n[map a port before](#port-mapping), otherwise the underlying system won't be\nable to trigger the HTTP call.\n\n###### Example\n```purescript\nmain :: Effect Unit\nmain = launchAff_ $ do\n  upped \u003c- startContainer (setWaitStrategy [ HttpStatusCode \"/\" 80 200 ] \u003c\u003c\u003c setExposedPorts [ 80 ] $ mkContainer \"nginx:alpine\")\n  -- do something with the container\n  void $ stopContainer\n```\n\n##### HttpResponsePredicate\nSimilar to the [HttpStatusCode](#httpstatuscode), this Wait Strategy will\ninteract with the underlying container via HTTP. The constructor takes 3\nparameters: the `path` where to send the HTTP request to, the `port` and a\nfunction which takes a `String` and returns a `Boolean`, the `String` is the\nraw HTTP body returned by the service. The service is considered to be ready\nif the predicate returns `true`.\n\n###### Example\n```purescript\nmain :: Aff Unit\nmain = do\n  let config =\n    setWaitStrategy [ HttpResponsePredicate \"/\" 80 (\\s -\u003e \"welcome to nginx\" `includes` s) ]\n      \u003c\u003c\u003c setExposedPorts [ 80 ]\n\n  started \u003c- startContainer (config $ mkContainer \"nginx:alpine\")\n  -- do something with it\n  void $ stopContainer started\n```\n\n##### ShellCommand\nThis final constructor is `ShellCommand`, it takes a single parameter which is\na shell script to be periodically launched inside the container. It will stop\nwhen either one of the following conditions is met:\n- the timeout occurs (see [configuring the timeout](#configuring-the-timeout)\n- the shell script completes successfully (i.e. its exit code is 0)\n\n#### Environment variables\nIt is possible to inject environment variables inside the container using the\nfunction:\n```purescript\ntype KV = { key :: String, value :: String }\nsetEnvironment :: Array KV -\u003e TestContainer -\u003e TestContainer\n```\n\nThe variables will be available immediately, it is the exact equivalent to the\nflag `-e` of the `docker run` command.\n\n#### Privileged Mode\nIf you need your container to run in `privileged` mode, you can do so with the\nfunction\n```purescript\nsetPrivilegedMode :: TestContainer -\u003e TestContainer\n```\n\nEquivalent to the `--privileged` flag of `docker run`.\n\n#### Capabilities\nAll the Linux capabilities can be added or removed before starting the container,\nthis is the list of the constructors.\n\n**WARNING**: not all of them have been tested!\n\n```purescript\ndata Capability\n  = AuditControl\n  | AuditRead\n  | AuditWrite\n  | BlockSuspend\n  | BPF\n  | CheckpointRestore\n  | Chown\n  | DACOverride\n  | DACReadSearch\n  | FOwner\n  | FSetID\n  | IPCLock\n  | IPCOwner\n  | Kill\n  | Lease\n  | LinuxImmutable\n  | MACAdmin\n  | MACOverride\n  | MkNod\n  | NetAdmin\n  | NetBindService\n  | NetBroadcast\n  | NetRaw\n  | PerfMon\n  | SetGid\n  | SetFCap\n  | SetPCap\n  | SetUid\n  | SysAdmin\n  | SysBoot\n  | SysChroot\n  | SysModule\n  | SysNice\n  | SysPAcct\n  | SysPTrace\n  | SysRawIO\n  | SysResource\n  | SysTime\n  | SysTtyConfig\n  | WakeAlarm\n```\n\nTwo functions are available to add or remove a capability:\n```purescript\nsetAddedCapabilities :: Array Capability -\u003e TestContainer -\u003e TestContainer\nsetDroppedCapabilities :: Array Capability -\u003e TestContainer -\u003e TestContainer\n```\n\n#### Set User\nIt is also possible to change the default user of the container:\n```purescript\nsetUser :: User -\u003e TestContainer -\u003e TestContainer\n```\n#### Set Command\nIt is possible to set the command to be launched upon starting the container, this\nis passed to the `entrypoint` of the configured underlying container (if any)\n```purescript\nsetCommand :: Array String -\u003e TestContainer -\u003e TestContainer\n```\n\n(An example is provided in the following paragraph)\n\n\n#### Exec a command\nOnce a container is started, you can `exec` commands inside using the\nfollowing function:\n```purescript\ntype ExecResult = { output :: String, exitCode :: Int }\nexec :: ∀ m. MonadAff m =\u003e Array String -\u003e TestContainer -\u003e m (Either String ExecResult)\n```\n\nThe `Array String` parameter is passed directly to `execve` so it has to\nconform to that standard.\n\n\n##### Example\n```purescript\nexecCommandTest :: Aff Unit\nexecCommandTest = do\n  cnt \u003c- startContainer (setCommand [ \"sleep\", \"infinity\" ] $ mkContainer \"alpine:latest\")\n  case cnt of\n    Right c -\u003e do\n      execResultE \u003c- exec [ \"ls\", \"/\" ] cnt\n      case execResultE of\n        Right { output, exitCode } -\u003e do\n          -- do something with output and exitCode\n          pure unit\n        Left _ -\u003e pure unit\n    Left _ -\u003e pure unit\n```\n\n\n#### Use the `withContainer` helper\nTo make it easier to interact with containers, and to avoid the hassle of\nhaving to either `start` and `stop` them on your own or to `bracket` somehow, a\nfunction is provided:\n```purescript\nwithContainer :: ∀ m a. (MonadAff m) =\u003e TestContainer -\u003e (TestContainer -\u003e m a) -\u003e m (Either String a)\n```\n\nThis function takes a `GenericContainer` as first parameter, a function acting\nwith it as the second parameter and returns an `Either` of a `String` (the\ndefault error type) or the result of the executed action.\n\n##### Example\n```purescript\ntestWithContainer :: Aff Unit\ntestWithContainer = do\n  let cnt = setCommand [ \"sleep\", \"infinity\" ] $ mkContainer \"alpine:latest\"\n  res \u003c- withContainer cnt $ \\c -\u003e do\n    exec [ \"ls\", \"/\" ] c\n\n  case res of\n    Left e -\u003e Console.log $ \"An error occured: \" \u003c\u003e e\n    Right { output, exitCode } -\u003e do\n      Console.log $ \"Exec output: \" \u003c\u003e output \u003c\u003e \", exitCode: \" \u003c\u003e show exitCode\n```\n\nThe snippet above will start and stop the container automatically, after the\n`exec` of the `ls /` command.\n\n\n## Network\nThis library provides a couple of function for creating, handling and attaching\nnetworks to containers. This will allow you to create separated services and to\nallow those services to communicate with each other easily.\n\nAs usual, the [test folder](./test/Test/Network.purs) contains some integration\ntests which will tell you how to use those functions.\n\n### Create a network\nCreating a network is similar to the [creation of a\ncontainer](#create-container), although the constructor is public, it is better\nto use the *smart constructor* defined as follows:\n```purescript\nmkNetwork :: Network\n```\n\nThe smart constructor takes no parameter and will create a `GenericNetwork`.\n\n### Start a network\nOnce you have created your network, and before being able to attach it to\nexisting containers, you **must** start it using the following function:\n```purescript\nstartNetwork :: ∀ m. MonadAff m =\u003e Network -\u003e m (Either String Network)\n```\n\nThis will return you a `StartedNetwork` or a `String` with an error message.\n\nPlease not that **the network is stopped automatically** by Testcontainers when\nit is no longer used.\n\n### Attach a container to a network\nWith a [`StartedNetwork`](#start-a-network), you can attach containers to it\nusing the following function:\n```purescript\nsetNetwork :: Network -\u003e TestContainer -\u003e TestContainer\n```\n\nYou can attach multiple containers to the network, it is also advised to use\nthe `setNetworkAliases` function in order to be able to refer to other\ncontainers in the same network using an easy-to-remember name:\n```purescript\nsetNetworkAliases :: Array String -\u003e TestContainer -\u003e TestContainer\n```\n\n#### Example\n```purescript\nnetworkTest :: Aff Unit\nnetworkTest = do\n  commonNetwork \u003c- startNetwork mkNetwork\n  case commonNetwork of\n    Left e -\u003e Console.log $ \"Error: \" \u003c\u003e e\n    Right network -\u003e do\n      firstAlpine \u003c- mkAffContainer \"alpine:latest\" $\n        setCommand [ \"sleep\", \"infinity\" ]\n          \u003c\u003c\u003c setNetwork network\n          \u003c\u003c\u003c setNetworkAliases [ \"firstAlpine\" ]\n\n      secondAlpine \u003c- mkAffContainer \"alpine:latest\" $\n        setCommand [ \"sleep\", \"infinity\" ]\n          \u003c\u003c\u003c setNetwork network\n          \u003c\u003c\u003c setNetworkAliases [ \"secondAlpine\" ]\n\n      void $ withContainer firstAlpine $ \\c -\u003e\n        void $ withContainer secondAlpine $ \\c' -\u003e do\n          case (exec [ \"getent\", \"hosts\", \"secondAlpine\" ] c) of\n            Left e -\u003e Console.log $ \"Error: \" \u003c\u003e e\n            Right { output, exitCode } -\u003e do\n              Console.log $ \"Exec output: \" \u003c\u003e output \u003c\u003e \", exitCode: \" \u003c\u003e show exitCode\n\n          case (exec [ \"getent\", \"hosts\", \"firstAlpine\" ] c') of\n            Left e -\u003e Console.log $ \"Error: \" \u003c\u003e e\n            Right { output, exitCode } -\u003e do\n              Console.log $ \"Exec output: \" \u003c\u003e output \u003c\u003e \", exitCode: \" \u003c\u003e show exitCode\n  where\n  mkAffContainer :: ∀ a m. IsImage a =\u003e MonadAff m =\u003e a -\u003e (TestContainer -\u003e TestContainer) -\u003e m TestContainer\n  mkAffContainer img conf = pure \u003c$\u003e conf $ mkContainer img\n```\n\n## Docker Compose\n`Testcontainers` supports `docker-compose` format file and allows the creation\nand handling of environments quite easily. There are some *caveats* though, for\nexample it will be up to the developers to expose the needed ports for the\nservices and to guarantee that there will be no binding conflicts. Whenever it\nis possible, prefer creating your containers using the [containers'\nAPI](#create-container) instead.\n\n### Create an environment\nJust like everything else, the constructors for the `DockerComposeEnvironment`\nare open but it is better to use the *smart constructor*:\n```purescript\nmkComposeEnvironment :: FilePath -\u003e (Array FilePath) -\u003e DockerComposeEnvironment\n```\nThe constructor takes 2 parameters, a `FilePath` pointing to the root of your\nenvironment's context and an `Array String` of the compose files which will be\nused.\n\n### Up an environment\nOnce you have created your environment using the *smart constructor* described\nabove, you can start it easily using the function `composeUp`:\n```purescript\ncomposeUp :: ∀ m. MonadAff m =\u003e DockerComposeEnvironment -\u003e m (Either String DockerComposeEnvironment)\n```\nThis function returns an `Either` of a `String` describing the error or a newly\ncreated, started docker compose environment.\n\n### Down an environment\nTo stop a running environment:\n```purescript\ncomposeDown :: ∀ m. MonadAff m =\u003e DockerComposeEnvironment -\u003e m (Either String DockerComposeEnvironment)\n```\nBe aware that it is **not possible** to restart a stopped environment, you will\nneed to create a new one.\n\n### Use the `withCompose` helper\nTo avoid the hassle of having to remember to stop your environment, a\nbracketing function is provided, very similar to the\n[`withContainer`](#use-the-withcontainer-helper) one described a little earlier\nfor containers.\n```purescript\nwithCompose :: ∀ m a. MonadAff m =\u003e DockerComposeEnvironment -\u003e (DockerComposeEnvironment -\u003e m a) -\u003e m (Either String a)\n```\n\n### Get a container from the environment\nOnce your environment is up \u0026 running you can play with the included containers\nusing the provided function:\n```purescript\ngetContainer :: ∀ m. MonadEffect m =\u003e DockerComposeEnvironment -\u003e String -\u003e m (Either String TestContainer)\n```\nThe `String` parameter is the name of the service from which you want to\nretrieve the container, note that if you're using `compose` version \u003c 1.6 the\nname of the service will have a `_1` suffixed while with compose \u003e= 1.6 it will\nbe `-1`.\n\nIf your compose file looks like this:\n```yaml\nversion: \"3.6\"\nservices:\n  nginx:\n    image: nginx:alpine\n```\nThen the name of the service will either be:\n- `nginx_1` if you're using compose \u003c 1.6 or\n- `nginx-1` if using a more recent version of compose\n\nTo avoid having to unwrap the `Either` every time, a commodity function is available:\n```purescript\nwithComposeContainer :: ∀ m a. MonadAff m =\u003e DockerComposeEnvironment -\u003e String -\u003e (TestContainer -\u003e m a) -\u003e m (Either String a)\n```\n\n### Set Wait strategies for containers\nIt is possible to define a [wait strategy](#set-a-wait-strategy) for a specific\ncontainer which is part of the Compose environment using the provided function:\n\n```purescript\nsetWaitStrategy :: Array WaitStrategy -\u003e String -\u003e DockerComposeEnvironment -\u003e DockerComposeEnvironment\n```\n\nThe second parameter of the function is the name of the service in the compose\nenvironment.\n\n### Use Profiles\nIn the Compose specifications it is possible to define different profiles in\nthe same docker compose file, for example:\n\n```yaml\nversion: '3.7'\n\nservices:\n  postgres:\n    image: postgres:14-alpine\n    profiles: [ \"db\" ]\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: postgres\n      POSTGRES_DB: postgres\n  redis:\n    image: redis:alpine\n    profiles: [ \"cache\" ]\n    user: redis\n  alpine:\n    image: alpine:latest\n    command: [ \"/bin/sh\", \"-c\", \"sleep infinity\" ]\n    profiles: [ \"backend\" ]\n```\n\nHere, three profiles have been defined: **db**, **cache** and **backend**, each\nprofile will allow the creation of one or more service. You can specify which\nprofiles you want to use when creating your docker compose environment using\nthe provided function:\n\n```purescript\nsetProfiles :: Array String -\u003e DockerComposeEnvironment -\u003e DockerComposeEnvironment\n```\n\n### Automatically rebuild\nIt is possible to tell to Testcontainers to automatically\nrebuild the images needed for your services each time you\n`up` your environment by using the following function:\n\n```purescript\nsetRebuild :: DockerComposeEnvironment -\u003e DockerComposeEnvironment\n```\n\nIn order for this to work you need to use the `build`\ndefinition in your compose file, just like in the example\nbelow:\n\n```yaml\nversion: '3.7'\nservices:\n  builtRedis:\n    build:\n      tags: [ \"built-redis:latest\" ]\n      context: .\n      dockerfile: ./Dockerfile.redis\n    image: built-redis:latest\n  builtNginx:\n    build:\n      tags: [ \"built-nginx:latest\" ]\n      context: .\n      dockerfile: ./Dockerfile.nginx\n    ports:\n      - target: 80\n        published: 8080\n        protocol: tcp\n    image: built-nginx:latest\n```\n\nThe full example is available [in the tests folder](./test/compose/rebuildable/).\n\n### Use Environment variables\nSometimes you want your `compose` file to be dynamically\ninterpreted using some environment variables, this is\npossible with `Testcontainers` in two different ways.\nFirst, let's create our dynamic compose file as follows:\n\n```yaml\nversion: '3.7'\nservices:\n  alpine:\n    image: alpine:${ALPINE_TAG}\n    command: [\"/bin/sh\", \"-c\", \"sleep infinity\"]\n    environment:\n      SOMEVARIABLE: \"${SOMEVARIABLE}\"\n      QUOTEDVARIABLE: \"${QUOTEDVARIABLE}\"\n```\n\nFor this example to work we need to provide a definition\nfor the three environment variables, otherwise\nTestcontainers will refuse to `up` our environment.\n\n#### From Files\nThe first option we have is to define our variables in a\n`dotenv` file (i.e. a file where each line has the syntax\n`VARIABLE=VALUE`) and then use the function below:\n\n```purescript\nsetEnvironmentFile :: FilePath -\u003e DockerComposeEnvironment -\u003e DockerComposeEnvironment\n```\n\nPlease notice that the first parameter is **relative to\nthe root of our environment**, so if we have defined our\nenvironment using the following call:\n```purescript\n-- Remember:\n-- mkComposeEnvironment :: FilePath -\u003e (Array FilePath) -\u003e DockerComposeEnvironment\nlet myEnv = mkComposeEnvironment \"./test/compose/environmentfile” [ \"compose.yaml\" ]\n```\n\nAnd we want to use the `.env.custom` file located in\n`./test/compose/environmentfile` all we need to do is:\n\n```purescript\nlet withEnvFile = setEnvironmentFile \".env.custom\" env\n```\n\nAs usual, a full working example is available in the\n[tests folder](./test/compose/environmentfile) of this\nrepository.\n\n#### From Code\nFinally, it is also possible to set the environment\nvariables programmatically using the following function:\n\n```purescript\nsetEnvironment :: Array KV -\u003e DockerComposeEnvironment -\u003e DockerComposeEnvironment\n```\n\nFor example, to fulfill the file described at the\nbeginning of this chapter we could simply:\n\n```purescript\n-- Remember:\n-- mkComposeEnvironment :: FilePath -\u003e (Array FilePath) -\u003e DockerComposeEnvironment\nlet myEnv = mkComposeEnvironment \"./test/compose/environmentfile” [ \"compose.yaml\" ]\nlet filledEnv =\n  setEnvironment\n    [ { key: \"ALPINE_TAG\", value: \"latest\" }\n    , { key: \"SOMEVARIABLE\", value: \"v\" }\n    , { key: \"QUOTEDVARIABLE\", value: \"x\" } ]\n    myEnv\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmassix%2Fpurescript-testcontainers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmassix%2Fpurescript-testcontainers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmassix%2Fpurescript-testcontainers/lists"}