Duration: ~15 min
Goal: Integrate Floci into a realistic developer loop and a CI pipeline using Testcontainers.
Edit code → Run tests (Floci in Docker / Podman) → Fix → Repeat
↑ fast, no cloud, no cost
The goal is to never need a real AWS account during development or testing.
The compose.yaml is the same regardless of runtime — only the command differs.
# compose.yaml
services:
app:
build: .
environment:
AWS_ENDPOINT_URL: http://floci:4566
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
depends_on:
floci:
condition: service_healthy
floci:
image: floci/floci:latest
ports:
- "4566:4566"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_floci/health"]
interval: 5s
timeout: 3s
retries: 10Start your full stack:
| Runtime | Command |
|---|---|
| Docker | docker compose up |
| Podman | podman compose up |
💡 macOS + Podman: ensure the Podman machine is running first:
podman machine start
💡 Windows: run these commands inside your WSL2 terminal.
Floci has first-class Testcontainers support. Your tests spin up a fresh Floci instance per test suite — fully isolated, no shared state.
The testcontainers-demo/ directory in this repo has a complete working example. Try it now:
Step 1 — Set DOCKER_HOST so Testcontainers finds Podman (macOS)
export DOCKER_HOST=$(podman machine inspect --format 'unix://{{.ConnectionInfo.PodmanSocket.Path}}')
export TESTCONTAINERS_RYUK_DISABLED=true⚙️ All platforms:
Platform Runtime Command macOS Docker Desktop (no change needed — socket auto-detected) macOS Podman export DOCKER_HOST=$(podman machine inspect --format 'unix://{{.ConnectionInfo.PodmanSocket.Path}}')Windows (WSL2) Docker Desktop export DOCKER_HOST=unix:///var/run/docker.sockWindows (WSL2) Podman export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sockFor Podman on any platform, also set:
export TESTCONTAINERS_RYUK_DISABLED=true(Ryuk resource-reaper is not supported by Podman).
Step 2 — Install and run
cd testcontainers-demo
npm install
npm testExpected output:
PASS src/s3.test.ts
S3
✓ creates a bucket and lists it
✓ puts and gets an object
PASS src/sqs.test.ts
SQS
✓ sends and receives a message
Tests: 3 passed, 3 total
Each test suite starts its own Floci container, runs, then tears it down — zero shared state, zero cloud.
testcontainers-demo/src/s3.test.ts:
import { FlociContainer, StartedFlociContainer } from "@floci/testcontainers";
import { S3Client, CreateBucketCommand, PutObjectCommand, GetObjectCommand, ListBucketsCommand } from "@aws-sdk/client-s3";
describe("S3", () => {
let floci: StartedFlociContainer;
let s3: S3Client;
beforeAll(async () => {
floci = await new FlociContainer().start();
s3 = new S3Client({
endpoint: floci.getEndpoint(),
region: floci.getRegion(),
credentials: {
accessKeyId: floci.getAccessKey(),
secretAccessKey: floci.getSecretKey(),
},
forcePathStyle: true,
});
});
afterAll(async () => {
await floci.stop();
});
it("creates a bucket and lists it", async () => {
await s3.send(new CreateBucketCommand({ Bucket: "workshop-bucket" }));
const { Buckets } = await s3.send(new ListBucketsCommand({}));
expect(Buckets?.some((b) => b.Name === "workshop-bucket")).toBe(true);
});
it("puts and gets an object", async () => {
await s3.send(new PutObjectCommand({ Bucket: "workshop-bucket", Key: "hello.txt", Body: "Hello, Floci!" }));
const { Body } = await s3.send(new GetObjectCommand({ Bucket: "workshop-bucket", Key: "hello.txt" }));
expect(await Body!.transformToString()).toBe("Hello, Floci!");
});
});Install:
pip install testcontainers-floci boto3Test:
import boto3
from testcontainers_floci import FlociContainer
def test_s3_create_bucket():
with FlociContainer() as floci:
s3 = boto3.client(
"s3",
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
s3.create_bucket(Bucket="test-bucket")
buckets = [b["Name"] for b in s3.list_buckets()["Buckets"]]
assert "test-bucket" in buckets<!-- pom.xml -->
<dependency>
<groupId>io.floci</groupId>
<artifactId>testcontainers-floci</artifactId>
<version>LATEST</version>
<scope>test</scope>
</dependency>@Testcontainers
class S3IntegrationTest {
@Container
static FlociContainer floci = new FlociContainer();
@Test
void shouldCreateBucket() {
S3Client s3 = S3Client.builder()
.endpointOverride(URI.create(floci.getEndpoint()))
.region(Region.of(floci.getRegion()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(floci.getAccessKey(), floci.getSecretKey())))
.forcePathStyle(true)
.build();
s3.createBucket(b -> b.bucket("my-bucket"));
assertThat(s3.listBuckets().buckets())
.anyMatch(b -> b.name().equals("my-bucket"));
}
}# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
floci:
image: floci/floci:latest
ports:
- 4566:4566
options: >-
--health-cmd "curl -f http://localhost:4566/_floci/health"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run tests
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_DEFAULT_REGION: us-east-1
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
run: npm test💡 No secrets needed. No AWS credentials. No IAM roles.
The same pipeline works for any team member and costs $0 to run.
Pre-seed Floci with resources on startup using init scripts (requires floci/floci:latest-compat):
# compose.yaml
services:
floci:
image: floci/floci:latest-compat
volumes:
- ./init:/etc/localstack/init/ready.d:ro# init/01-seed.sh
#!/bin/sh
set -eu
aws sqs create-queue --queue-name orders
aws s3 mb s3://app-bucket
aws ssm put-parameter --name /app/config --type String --value production
aws dynamodb create-table \
--table-name Users \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUESTEvery time Floci starts, your environment is pre-wired and ready.
| Practice | Benefit |
|---|---|
| Floci as Podman / Docker Compose sidecar | Full stack locally, zero cloud |
| Testcontainers per test suite | Isolated, repeatable, fast |
| GitHub Actions service container | CI with no AWS account/secrets |
| Init hooks | Pre-seeded environment every run |
➡️ Next: Lab 07 — Floci vs. the World