Course outline

GitHub Actions / Reusable automation and scoped authority / Lesson 12

Job containers, service containers, and network paths

Reason about where the test client runs before selecting a service address or publishing a port.

About 4 min readContent reviewed
On this page

Lesson goals

  • Distinguish container jobs from container actions and services.
  • Explain host-port and same-network addressing without starting a service.

Three container roles have different contracts

A job container provides the execution environment for supported job steps. A Docker container action packages one action implementation. A service container provides a dependency such as a database or cache for the job. They are related mechanisms, not interchangeable names. GitHub’s documented container-job and service-container support requires a suitable Linux runner and Docker support; hosted and self-hosted prerequisites differ. Parcel’s examples remain diagrams and fragments only. No Docker daemon, image pull, database, or cache service is contacted or started.

Locate the client first

When a job runs in a container, its service containers share a configured Docker network and the service label can be used as the hostname with the service’s internal port. When steps run directly on the runner machine, access normally uses localhost and a mapped host port under the documented model. localhost inside a container refers to that container, not the service. Choosing an address before identifying the client’s network location causes many false connection diagnoses and unnecessary port exposure.

Published ports belong to the host boundary

A declared service port mapping can select a fixed host port or let Docker choose an available host port under supported syntax. The job.services context exposes assigned port information for a runner-hosted client. This is different from the service’s internal listener port. The following fragment illustrates a cache service declaration only. redis:7 is a mutable teaching tag, not a reviewed immutable release pin or a claim about the newest Redis version. No successful service readiness or connection is asserted.

yaml A non-runnable service declaration fragment
services:
  cache:
    image: redis:7
    ports:
      - 6379/tcp
    options: >-
      --health-cmd "redis-cli ping"
      --health-interval 10s
      --health-timeout 5s
      --health-retries 5

The enclosing Linux job and its runner prerequisites are omitted. A host-side client would read the assigned mapping through job.services.cache.ports[6379]; a container-side client would normally use cache:6379. The health command is illustrative and not executed.

Process start and application readiness differ

A container can be running before its dependency accepts the operations a test needs. A health check can provide a useful signal, but it must match the dependency’s actual readiness contract and remain bounded. Avoid arbitrary sleep as the only synchronization mechanism. Test setup may also need a fresh schema, fixture data, or protocol negotiation after transport readiness. Keep synthetic lab credentials separate from real systems and never reuse operational data to make integration tests more realistic. Service lifecycle cleanup is not permission to delete unrelated persistent storage.

Describe addresses without making connections

The complete local model returns the appropriate conceptual address for a declared client location. It uses a synthetic assigned host port and performs no socket or network operation. Real runner mapping and network behavior must be observed in an authorized environment. A correct address still does not prove name resolution, listener binding, health, authentication, or application semantics. Diagnose those layers separately and retain the precise error: connection refusal, timeout, DNS failure, and protocol rejection describe different boundaries.

python A synthetic addressing model
def address(client_location, assigned_host_port):
    if client_location == "job-container":
        return "cache:6379"
    if client_location == "runner-host":
        return f"127.0.0.1:{assigned_host_port}"
    raise ValueError("unknown client location")
print(address("job-container", 16379))
print(address("runner-host", 16379))
Expected output
cache:6379
127.0.0.1:16379

The port 16379 is declared input, not an observed Docker mapping. The program starts no service and establishes no connection.

Common errors and fixes

A container job connects to localhost for its service.

Why it happens
The client’s loopback was mistaken for the service endpoint.
How to fix it
Use the service label and internal port under the documented shared-network model.
Prevent it next time
Draw client and service locations before selecting addresses.

A host-side client uses the internal port despite dynamic mapping.

Why it happens
Container and assigned host ports were conflated.
How to fix it
Read the actual mapping through the supported context.
Prevent it next time
Keep internal listener and host publication identities separate.

A running service is assumed ready for fixture operations.

Why it happens
Process existence was treated as full application readiness.
How to fix it
Use a bounded readiness contract and explicit test setup.
Prevent it next time
Distinguish transport, authentication, schema, and business readiness.

Check your understanding

1. A containerized test client and cache share the job network. Must the cache publish a host port?

Show worked solution

Not solely for that same-network communication. The client normally uses the service label and internal port. Host publication serves a different boundary and should be added only when a host-side consumer actually needs it.

2. A health check succeeds but tests report a missing table. Is networking necessarily broken?

Show worked solution

No. The dependency can be reachable and healthy at its transport or process layer while application fixture setup is incomplete. Verify schema and test initialization through the authorized lab contract rather than widening network exposure or restarting blindly.

Key takeaways

  • Locate the client before choosing hostname and port.
  • Service lifecycle and readiness require explicit bounded assumptions.

Official references