Package your software

Antithesis runs your software using Linux container images, much as Docker and Kubernetes do. If you or your customers already deploy your software via containers, there’s a decent chance your existing images will work out of the box.

However there are a few common gotchas that we’ll go through here, the biggest of which is that Antithesis runs your system in a hermetic simulation environment – it will have no internet access. (Think of it as giving your software an internet sabbatical.)

Everything your system depends upon must also be deployed into that environment, or suitably mocked.

For most customers, this is actually the most difficult part of using Antithesis, but the good news is that you have to do it approximately once. If you get stuck, please don’t hesitate to contact us.

1. Containerize your software

First, build a container image that includes your software and any of its dependencies. Again, if your software is already being deployed in containers, there’s a decent chance your existing images will just work.

Antithesis runs your software on x86-64 CPUs. Ensure that your software is compiled for this architecture by building containers with the following option: docker build --platform linux/amd64.

If you operate a microservices architecture, you should provide a separate container for each of your services, though it’s fine if they share intermediate layers.

If your services perform network calls to the public web on startup, for example to fetch a software dependency or data file, you should move this into the container build process instead – remember, your software will be running without access to the internet.

For example, DON’T do this:

FROM docker.io/ubuntu:latest
COPY my_app /opt/my_app
CMD curl --fail https://example.com/data > /opt/data && /opt/my_app /opt/data

Do this instead:

FROM docker.io/ubuntu:latest
COPY my_app /opt/my_app
RUN curl --fail https://example.com/data > /opt/data
CMD /opt/my_app /opt/data

2. Containerize your dependencies

Any dependencies must also be provided in the Antithesis Environment, because, once again, it will have no internet access once testing begins. (Just like it’s in high school.)

There are two ways to do this.

  1. Provide the actual service, just as you would in production.
  2. Upload a mock of the service.

Uploading the actual service

If the dependency is something you control, e.g. local database node, it’s generally easiest to just put it in a container and upload that to our registry along with your software. Alternatively, you can upload the container to a public registry like Docker Hub and then specify it in your Docker Compose file

If it’s a third-party dependency, many open-source services, (e.g. Redis, Kafka, MongoDB, MySQL etc.) are already distributed in containers. Simply specify the container in your Docker Compose file – there’s an example below.

Uploading a mock

Many companies provide mocks in containerized form (e.g. Stripe provides Stripe-mock), and third party providers, e.g. Localstack, provide sophisticated pre-built mocks of common dependencies like AWS. Simply specify the container in your Docker Compose file, e.g.:

infra.localstack:
  image: localstack/localstack:3.5.0
  container_name: localstack
  hostname: localstack
  environment:
    SERVICES: s3
    DEBUG: 0
    LS_LOG: warn
    SKIP_SSL_CERT_DOWNLOAD: 1
    DISABLE_EVENTS: 1
    LOCALSTACK_HOST: localstack
    DEFAULT_REGION: us-east-1
  volumes:
    - /var/lib/localstack:/var/lib/localstack
    - /var/run/docker.sock:/var/run/docker.sock
  networks:
    spark-net:
      ipv4_address: 10.0.0.12

We provide a partial list of commonly used ones in Handling External Dependencies for your convenience.

As a last resort, you can write your own mock. If you do this, you’ll need to containerize and upload the mock alongside your software.

Either way, you’ll need to create any resources the mock needs in your Docker Compose file as described below.

For a more thorough discussion of this step, please see Handling External Dependencies.

3. Create a configuration directory

Next you’ll need to create a directory in your local filesystem, called config, containing:

  • Any configuration files expected by the containers: license files, settings, or other resources.
  • A container orchestration file such as docker-compose.yaml.

Here are the contents of our example configuration directory:

$ ls -a config/
.
..
docker-compose.yaml
license

This directory will also get packaged as a container image and uploaded, but for now, you should just create it locally.

4. Set up container orchestration

Antithesis supports a few different orchestration technologies, but the one that’s easiest to set up is Docker Compose. This involves creating a file called docker-compose.yaml that lists each of the services you want to have running, the container image that service should be started from, any external volumes that should be mounted into that image, and other options.

We recommend following this example closely, as there are a few important details to this that we discuss below.

version: '3.0'

services:
  application1:
    container_name: application1
    hostname: application1
    image: mycompany/app:antithesis-tag
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.1

  application2:
    container_name: application2
    hostname: application2
    image: mycompany/app:antithesis-tag
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.2

  database:
    container_name: database
    hostname: database
    image: docker.io/mysql:latest
    volumes: 
      - ./volumes/database:/usr/bin/database/data
    networks:
      antithesis-net:
        ipv4_address: 10.20.20.3

networks:
  antithesis-net:
    driver: bridge
    ipam:
      config:
      - subnet: 10.20.20.0/24

A few items to note in this example:

  • Two of the services have requested external volume mounts. Data stored in these directories will be mapped out of the container filesystem, and will outlive the container’s own lifetime. The volume declarations refer to empty directories created in the configuration directory (see above). Since we’re going to use the configuration directory as our working directory, these relative paths will resolve correctly.
  • In addition to network address, the services in the above example can reach each other by hostname. Our container orchestration will automatically generate suitable DNS entries from the names of each of the services, and inject them into the environment of every other service.

Do not change the logging driver in your docker-compose.yaml, as Antithesis relies on the default logging driver. If you do specify a custom logging driver, Antithesis will generate logs as normal, but not display them in the debugging artifacts we send you.

This is often the trickiest part of the process for users who don’t already deploy software in containers. If you simply follow the example above, you should be on solid ground, but you can also contact us if you need help here, or refer to the Docker Compose documentation.

5. Validating what you have so far

If you’ve followed the steps above, you should now be able to test your entire setup locally before deploying it to the Antithesis environment. Running docker-compose up from the root of your configuration directory should produce a running system.

However, we want to test that this all still works without any access to the internet. The easiest way to do this is to first enter into a network namespace before running the command:

$ pwd
/home/user/config

$ unshare -n

[2] $ docker-compose up
...
[Lots of output]
...

If everything still comes up and works, your services all find each other, then the hardest part is done! Now you’ll need to add a test template to make it go.

  • Introduction
  • How Antithesis Works
  • Getting started
  • Package your software
  • Make it go
  • Deploy to Antithesis
  • Launch a test run
  • User manual
  • Properties and Assertions
  • Properties in Antithesis
  • Assertions in Antithesis
  • Properties to Test For
  • Sometimes Assertions
  • Test Composer
  • Test Composer Basics
  • Test Composer Reference
  • Principles of Test Composition
  • Checking Test Templates Locally
  • Webhooks
  • Launching a test
  • Retrieving logs
  • Launching a debugging session
  • Webhook API
  • Reports
  • Triage report
  • Bug report
  • Multiverse debugging
  • Overview
  • Exploring the multiverse
  • Querying with event sets
  • The Environment and its utilities
  • Using the Antithesis Notebook
  • Cookbook
  • The Environment and Multiverse
  • The Antithesis Environment
  • Fault Injection
  • CPUID
  • Reference
  • Handling External Dependencies
  • SDK reference
  • Go
  • Tutorial
  • Instrumentor
  • Assert (reference)
  • Lifecycle (reference)
  • Random (reference)
  • Java
  • Tutorial
  • Instrumentation
  • Assert (reference)
  • Lifecycle (reference)
  • Random (reference)
  • C
  • C++
  • Tutorial
  • C/C++ Instrumentation
  • Assert (reference)
  • Lifecycle (reference)
  • Random (reference)
  • JavaScript
  • Python
  • Tutorial
  • Assert (reference)
  • Lifecycle (reference)
  • Random (reference)
  • Rust
  • Tutorial
  • Instrumentation
  • Assert (reference)
  • Lifecycle (reference)
  • Random (reference)
  • .NET
  • Languages not listed above
  • Assert (reference)
  • Lifecycle (reference)
  • Tooling integrations
  • CI integration
  • Discord and Slack integrations
  • Configuring Antithesis
  • Instrumentation
  • User management
  • Sizing your deployment
  • Best practices
  • Is Antithesis working?
  • Optimizing for Antithesis
  • Finding more bugs
  • FAQ
  • About Antithesis POCs
  • Release notes
  • Release notes