Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      May 31, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      May 31, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      May 31, 2025

      How To Prevent WordPress SQL Injection Attacks

      May 31, 2025

      Windows 11 version 25H2: Everything you need to know about Microsoft’s next OS release

      May 31, 2025

      Elden Ring Nightreign already has a duos Seamless Co-op mod from the creator of the beloved original, and it’ll be “expanded on in the future”

      May 31, 2025

      I love Elden Ring Nightreign’s weirdest boss — he bargains with you, heals you, and throws tantrums if you ruin his meditation

      May 31, 2025

      How to install SteamOS on ROG Ally and Legion Go Windows gaming handhelds

      May 31, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Oracle Fusion new Product Management Landing Page and AI (25B)

      May 31, 2025
      Recent

      Oracle Fusion new Product Management Landing Page and AI (25B)

      May 31, 2025

      Filament Is Now Running Natively on Mobile

      May 31, 2025

      How Remix is shaking things up

      May 30, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Windows 11 version 25H2: Everything you need to know about Microsoft’s next OS release

      May 31, 2025
      Recent

      Windows 11 version 25H2: Everything you need to know about Microsoft’s next OS release

      May 31, 2025

      Elden Ring Nightreign already has a duos Seamless Co-op mod from the creator of the beloved original, and it’ll be “expanded on in the future”

      May 31, 2025

      I love Elden Ring Nightreign’s weirdest boss — he bargains with you, heals you, and throws tantrums if you ruin his meditation

      May 31, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Run Integration Tests with GitHub Service Containers

    How to Run Integration Tests with GitHub Service Containers

    January 7, 2025

    Recently, I published an article about using Testcontainers to emulate external dependencies like a database and cache for backend integration tests. That article also explained the different ways of running the integration tests, environment scaffolding, and their pros and cons.

    In this article, I want to show another alternative in case you use GitHub Actions as your CI platform (the most popular CI/CD solution at the moment). This alternative is called Service Containers, and I’ve realized that not many developers seem to know about it.

    In this hands-on tutorial, I’ll demonstrate how to create a GitHub Actions workflow for integration tests with external dependencies (MongoDB and Redis) using the demo Go application we created in that previous tutorial. We’ll also review the pros and cons of GitHub Service Containers.

    Prerequisites

    • A basic understanding of GitHub Actions workflows.

    • Familiarity with Docker containers.

    • Basic knowledge of Go toolchain.

    Table of Contents

    • What are Service Containers?

    • Why not Docker Compose?

    • Job Runtime

    • Readiness Healthcheck

    • Private Container Registries

    • Sharing Data Between Services

    • Golang Integration Tests

    • Personal Experience & Limitations

    • Conclusion

    • Resources

    What are Service Containers?

    Service Containers are Docker containers that offer a simple and portable way to host dependencies like databases (MongoDB in our example), web services, or caching systems (Redis in our example) that your application needs within a workflow.

    This article focuses on integration tests, but there are many other possible applications for service containers. For example, you can also use them to run supporting tools required by your workflow, such as code analysis tools, linters, or security scanners.

    Why Not Docker Compose?

    Sounds similar to services in Docker Compose, right? Well, that’s because it is.

    But while you could technically use Docker Compose within a GitHub Actions workflow by installing Docker Compose and running docker-compose up, service containers provide a more integrated and streamlined approach that’s specifically designed for the GitHub Actions environment.

    Also, while they are similar, they solve different problems and have different general purposes:

    • Docker Compose is good when you need to manage a multi-container application on your local machine or a single server. It’s best suited for long-living environments.

    • Service Containers are ephemeral and exist only for the duration of a workflow run, and they’re defined directly within your GitHub Actions workflow file.

    Just keep in mind that the feature set of service containers (at least as of now) is more limited compared to Docker Compose, so be ready to discover some potential bottlenecks. We will cover some of them at the end of this article.

    Job Runtime

    You can run GitHub jobs directly on a runner machine or in a Docker container (by specifying the container property). The second option simplifies the access to your services by using labels you define in the services section.

    To run directly on a runner machine:

    .github/workflows/test.yaml

    jobs:
      integration-tests:
        runs-on: ubuntu-24.04
    
        services:
          mongo:
            image: mongodb/mongodb-community-server:7.0-ubi8
            ports:
              - 27017:27017
    
        steps:
          - run: |
              echo "addr 127.0.0.1:27017"
    

    Or you can run it in a container (Chainguard Go Image in our case):

    jobs:
      integration-tests:
        runs-on: ubuntu-24.04
        container: cgr.dev/chainguard/go:latest
    
        services:
          mongo:
            image: mongodb/mongodb-community-server:7.0-ubi8
            ports:
              - 27017:27017
        steps:
          - run: |
              echo "addr mongo:27017"
    

    You can also omit the host port, so the container port will be randomly assigned to a free port on the host. You can then access the port using the variable.

    Benefits of omitting the host port:

    • Avoids port conflicts – for example when you run many services on the same host.

    • Enhances Portability – your configurations become less dependent on the specific host environment.

    jobs:
      integration-tests:
        runs-on: ubuntu-24.04
        container: cgr.dev/chainguard/go:1.23
    
        services:
          mongo:
            image: mongodb/mongodb-community-server:7.0-ubi8
            ports:
              - 27017/tcp
        steps:
          - run: |
              echo "addr mongo:${{ job.services.mongo.ports['27017'] }}"
    

    Of course, there are pros and cons to each approach.

    Running in a container:

    • Pros: Simplified network access (use labels as hostnames), and automatic port exposure within the container network. You also get better isolation/security as the job runs in an isolated environment.

    • Cons: Implied overhead of containerization.

    Running on the runner machine:

    • Pros: Potentially less overhead than running the job inside a container.

    • Cons: Requires manual port mapping for service container access (using localhost:). There’s also less isolation/security, as the job runs directly on the runner machine. This potentially affects other jobs or the runner itself if something goes wrong.

    Readiness Healthcheck

    Prior to running the integration tests that connect to your provisioned containers, you’ll often need to make sure that the services are ready. You can do this by specifying docker create options such as health-cmd.

    This is very important – otherwise the services may not be ready when you start accessing them.

    In the case of MongoDB and Redis, these will be the following:

        services:
          mongo:
            image: mongodb/mongodb-community-server:7.0-ubi8
            ports:
              - 27017/27017
            options: >-
              --health-cmd "echo 'db.runCommand("ping").ok' | mongosh mongodb://localhost:27017/test --quiet"
              --health-interval 5s
              --health-timeout 10s
              --health-retries 10
    
          redis:
            image: redis:7
            ports:
              - 6379:6379
            options: >-
              --health-cmd "redis-cli ping"
              --health-interval 5s
              --health-timeout 10s
              --health-retries 10
    

    In the Action logs, you can see the readiness status:

    GitHub Actions Logs

    Private Container Registries

    In our example, we’re using public images from Dockerhub, but it’s possible to use private images from you private registries as well, such as Amazon Elastic Container Registry (ECR), Google Artifact Registry, and so on.

    Make sure to store the credentials in Secrets and then reference them in the credentials section.

    services:
      private_service:
        image: ghcr.io/org/service_repo
        credentials:
          username: ${{ secrets.registry_username }}
          password: ${{ secrets.registry_token }}
    

    Sharing Data Between Services

    You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host. But it’s not directly possible to mount the source code as a container volume. You can refer to this open discussion for more context.

    To specify a volume, you specify the source and destination path: <source>:<destinationPath>

    The <source> is a volume name or an absolute path on the host machine, and <destinationPath> is an absolute path in the container.

    volumes:
      - /src/dir:/dst/dir
    

    Volumes in Docker (and GitHub Actions using Docker) provide persistent data storage and sharing between containers or job steps, decoupling data from container images.

    Project Setup

    Before diving into the full source code, let’s set up our project for running integration tests with GitHub Service Containers.

    1. Create a new GitHub repository.

    2. Initialize a Go module using go mod init

    3. Create a simple Go application.

    4. Add integration tests in integration_test.go

    5. Create a .github/workflows directory.

    6. Create a file named integration-tests.yaml inside the .github/workflows directory.

    Golang Integration Tests

    Now as we can provision our external dependencies, let’s have a look at how to run our integration tests in Go. We will do it in the steps section of our workflow file.

    We will run our tests in a container which uses Chainguard Go image. This means we don’t have to install/setup Go. If you want to run your tests directly on a runner machine, you need to use the setup-go Action.

    You can find the full source code with tests and this workflow here.

    .github/workflows/integration-tests.yaml

    name: "integration-tests"
    
    on:
      workflow_dispatch:
      push:
        branches:
          - main
    
    jobs:
      integration-tests:
        runs-on: ubuntu-24.04
        container: cgr.dev/chainguard/go:latest
    
        env:
          MONGO_URI: mongodb://mongo:27017
          REDIS_URI: redis://redis:6379
    
        services:
          mongo:
            image: mongodb/mongodb-community-server:7.0-ubi8
            ports:
              - 27017:27017
            options: >-
              --health-cmd "echo 'db.runCommand("ping").ok' | mongosh mongodb://localhost:27017/test --quiet"
              --health-interval 5s
              --health-timeout 10s
              --health-retries 10
    
          redis:
            image: redis:7
            ports:
              - 6379:6379
            options: >-
              --health-cmd "redis-cli ping"
              --health-interval 5s
              --health-timeout 10s
              --health-retries 10
    
        steps:
          - name: Check out repository code
            uses: actions/checkout@v4
    
          - name: Download dependencies
            run: go mod download
    
          - name: Run Integration Tests
            run: go test -tags=integration -timeout=120s -v ./...
    

    To summarize what’s going on here:

    1. We run our job in a container with Go (container)

    2. We spin up two services: MongoDB and Redis (services)

    3. We configure healthchecks to make sure our services are “Healthy” when we run the tests (options)

    4. We perform a standard code checkout

    5. Then we run the Go tests

    Once the Action is completed (it took ~1 min for this example), all the services will be stopped and orphaned so we don’t need to worry about that.

    GitHub Actions Logs: full run

    Personal Experience & Limitations

    We’ve been using service containers for running backend integration tests at BINARLY for some time, and they work great. But the initial workflow creation took some time and we encountered the following bottlenecks:

    • It’s not possible to override or run custom commands in an action service container (as you would do in Docker Compose using the command property). Open pull request

      • Workaround: we had to find a solution that doesn’t require that. In our case, we were lucky and could do the same with environment variables.
    • It’s not directly possible to mount the source code as a container volume. Open discussion

      • While this is indeed a big limitation, you can copy the code from your repository into your mounted directory after the service container has started.

    Conclusion

    GitHub service containers are a great option to scaffold an ephemeral testing environment by configuring it directly in your GitHub workflow. With configuration being somewhat similar to Docker Compose, it’s easy to run any containerised application and communication with it in your pipeline. This ensures that GitHub runners take care of shutting everything down upon completion.

    If you use Github Actions, this approach works extremely well as it is specifically designed for the GitHub Actions environment.

    Resources

    • Source Code

    • GitHub Documentation

    • Discover more articles on packagemain.tech

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleHow to Help Someone with Their Code Using the Socratic Method
    Next Article Marvel Rivals Season 1 is bringing the Fantastic Four — here’s the Invisible Woman in action

    Related Posts

    Security

    New Linux Flaws Allow Password Hash Theft via Core Dumps in Ubuntu, RHEL, Fedora

    June 1, 2025
    Security

    Exploit details for max severity Cisco IOS XE flaw now public

    June 1, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    New Intel CPU Vulnerability ‘Indirector’ Exposes Sensitive Data

    Development

    CVE-2025-46524 – Stesvis WordPress CSRF Stored XSS

    Common Vulnerabilities and Exposures (CVEs)

    How Do You Access Google Password Manager on the Arc browser?

    Development

    Researchers from Sea AI Lab, UCAS, NUS, and SJTU Introduce FlowReasoner: a Query-Level Meta-Agent for Personalized System Generation

    Machine Learning

    Highlights

    Databases

    Leveraging Database Observability at MongoDB: Real-Life Use Case

    July 31, 2024

    This post is the second in our three-part series, Leveraging Database Observability at MongoDB. Welcome…

    The Experiment with Container Units in Design

    August 5, 2024

    Feeling Like I Have No Release: A Journey Towards Sane Deployments

    April 7, 2025

    CVE-2023-44755 – Sacco Management System SQL Injection

    April 22, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.