2026년 3월, LocalStack Community Edition이 인증 토큰을 필수로 요구하기 시작했다. 무료 사용자도 계정을 만들고 토큰을 발급받아야 하고, CI/CD 파이프라인에서는 사용량 제한까지 걸린다. 오래 쓰던 도구에 갑자기 진입 장벽이 생긴 셈이다. Floci는 바로 이 시점에 등장한 오픈소스 AWS 에뮬레이터다. Java와 Quarkus로 작성되었고 GraalVM Mandrel로 네이티브 바이너리로 컴파일된다. 시작 시간 24ms, 유휴 메모리 13MiB, Docker 이미지 90MB. 인증 토큰 없고 사용량 제한도 없다. 이번 포스팅에서는 Floci 설치부터 Spring Boot 프로젝트 통합, 성능 비교까지 정리하고자 한다.

LocalStack가 유료화되고 개발자에게 남은 선택지
LocalStack Community Edition은 2026년 3월부터 인증 토큰 없이는 실행 자체가 불가능해졌다. 무료 Hobby 티어가 존재하지만 CI/CD 환경에서의 사용 제한, 텔레메트리 수집, 보안 패치 중단 등 무시할 수 없는 제약이 따른다. 당연히 대안을 찾는 움직임이 나왔다.
대안 후보는 크게 세 가지다. 첫째, Python 기반 인프로세스 목 라이브러리인 Moto로 50개 이상의 AWS 서비스를 지원하지만 실제 Docker 컨테이너를 사용하지 않아 Lambda나 RDS 같은 컨테이너 서비스 테스트에는 한계가 있다. 둘째, Testcontainers는 컨테이너 오케스트레이션 프레임워크로 LocalStack이나 Floci를 감싸는 래퍼 역할을 한다. 셋째, Floci다. LocalStack과 동일한 포트(4566)를 사용하고, 동일한 wire protocol을 지원하기 때문에 기존 코드 변경 없이 Docker 이미지만 교체하면 된다.

Floci가 24ms 만에 뜨는 구조
Floci의 핵심 기술 스택은 Quarkus 프레임워크와 GraalVM Mandrel이다. Java로 작성된 코드를 네이티브 바이너리로 ahead-of-time(AOT) 컴파일하면 JVM 워밍업 없이 즉시 실행된다. 이름은 새털구름의 일종인 cirrocumulus floccus에서 따왔다. 작고 가벼운 구름 조각이라는 뜻인데, 실제로 메모리 사용량을 보면 이름값을 한다.
아키텍처는 세 계층으로 나뉜다. HTTP Router(JAX-RS/Vert.x)가 AWS SDK 요청을 받아 파싱하고, Service Layer에서 비즈니스 로직을 처리한 뒤, Storage Backend에 데이터를 저장한다. Service Layer는 다시 세 종류로 구분된다.
- Stateless 서비스: SSM, SQS, SNS, IAM, STS 등 — 상태를 유지하지 않는 서비스
- Stateful 서비스: S3, DynamoDB, API Gateway 등 — 데이터 영속성이 필요한 서비스
- Container 서비스: Lambda, ElastiCache, RDS — 실제 Docker 컨테이너로 실행되는 서비스
네이티브 빌드와 JVM 빌드 간 성능 차이를 보면 왜 GraalVM을 쓰는지 바로 납득이 된다. 네이티브 빌드는 시작 시간 24ms, 유휴 메모리 13MiB인 반면, JVM 빌드는 시작 시간 684ms, 유휴 메모리 78MiB로 약 28배의 시작 시간 차이가 난다. throughput은 네이티브 289 req/s, JVM 280 req/s로 거의 동일하다. 시작 속도와 메모리는 압도적으로 네이티브가 좋고, 처리량에서는 손해가 없다.
Quarkus와 GraalVM 네이티브 빌드에 대해 더 자세히 알고 싶다면 Quarkus Native Image 빌드 가이드: 0.049초 기동의 비밀을 참고하면 된다.

Docker Compose 한 줄로 Floci 시작하기
Floci 설치는 Docker Compose 파일 하나면 충분하다. LocalStack을 사용하던 프로젝트라면 이미지 이름만 바꾸면 된다.
services:
floci:
image: hectorvent/floci:latest
ports:
- "4566:4566"
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Lambda 컨테이너 실행에 필요
- ./data:/app/data # 데이터 영속성 (hybrid 모드)
environment:
FLOCI_STORAGE_MODE: hybrid # 인메모리 읽기 + 비동기 디스크 저장
FLOCI_STORAGE_PERSISTENT_PATH: /app/dataYAMLdocker compose up 한 번이면 모든 AWS 서비스가 http://localhost:4566에서 준비된다. hectorvent/floci:latest는 GraalVM 네이티브 바이너리 이미지로 90MB 크기이며 100ms 이내에 시작된다. JVM 기반이 필요하면 hectorvent/floci:latest-jvm 태그를 사용하면 된다. /var/run/docker.sock 마운트는 Lambda, ElastiCache, RDS처럼 실제 Docker 컨테이너를 띄우는 서비스에 필요하다.
AWS CLI로 바로 테스트해보자.
# 환경 변수 설정
export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
# S3 버킷 생성
aws s3 mb s3://my-test-bucket
# DynamoDB 테이블 생성
aws dynamodb create-table \
--table-name Users \
--attribute-definitions AttributeName=userId,AttributeType=S \
--key-schema AttributeName=userId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
# SQS 큐 생성
aws sqs create-queue --queue-name my-queueShellScript위 명령어는 실제 AWS CLI와 완전히 동일하다. AWS_ENDPOINT_URL 환경 변수 하나만 설정하면 AWS CLI가 모든 요청을 Floci로 보낸다. 인증 정보는 test/test처럼 아무 값이나 넣으면 된다. Floci는 인증 검증을 하지 않기 때문이다.

25개 AWS 서비스, SDK 테스트 408개 전부 통과
Floci는 2026년 4월 기준으로 25개 AWS 서비스를 지원한다. 공식 호환성 테스트 408개를 100% 통과하는데, LocalStack Community Edition이 같은 테스트 셋에서 305/383(80%)을 기록한 점을 생각하면 꽤 인상적인 수치다.
특히 LocalStack Community Edition에서는 유료 Pro 플랜에서만 사용 가능하거나 아예 지원하지 않는 서비스를 Floci는 무료로 제공한다.
| 서비스 | Floci | LocalStack CE |
|---|---|---|
| API Gateway v2 (HTTP API) | 16 operations | 미지원 |
| Cognito | 20 operations | 미지원 |
| ElastiCache (Redis/Valkey) | 9 operations | 미지원 |
| RDS (PostgreSQL/MySQL) | 14 operations | 미지원 |
| DynamoDB Streams | 12/12 통과 | 8/12 통과 |
| S3 Object Lock | 30/30 통과 | 24/30 통과 |
| SQS-Lambda ESM | 12/12 통과 | 10/12 통과 |
전체 서비스 목록은 S3, DynamoDB, SQS, SNS, Lambda, IAM, STS, KMS, Cognito, Secrets Manager, SSM Parameter Store, Kinesis, Step Functions, CloudFormation, EventBridge, CloudWatch Logs/Metrics, API Gateway v1/v2, ACM, SES, OpenSearch, ElastiCache, RDS를 포함한다. 각 서비스별 지원 operation 수는 Floci 공식 문서에서 확인할 수 있다.

Java AWS SDK v2로 Floci 연동하기
Java 프로젝트에서 Floci를 사용하려면 AWS SDK v2 클라이언트의 endpoint만 변경하면 된다. 먼저 의존성을 추가한다.
<!-- pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.30.9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
</dependency>
</dependencies>XMLAWS SDK BOM(Bill of Materials)을 사용하면 SDK 모듈 간 버전 충돌을 방지할 수 있다. 2026년 4월 기준 최신 안정 버전은 2.30.x 대다. 필요한 서비스 모듈만 개별적으로 추가하면 된다.
이제 DynamoDB 클라이언트를 Floci에 연결하는 코드를 작성한다.
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.net.URI;
import java.util.Map;
public class FlociDynamoDbExample {
// Floci 엔드포인트 — 로컬 개발 시에만 사용
private static final URI FLOCI_ENDPOINT = URI.create("http://localhost:4566");
public static void main(String[] args) {
// Floci용 DynamoDB 클라이언트 생성
DynamoDbClient dynamoDb = DynamoDbClient.builder()
.endpointOverride(FLOCI_ENDPOINT)
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("test", "test"))) // 인증값은 아무거나 허용
.build();
// 테이블 생성
dynamoDb.createTable(CreateTableRequest.builder()
.tableName("Orders")
.keySchema(KeySchemaElement.builder()
.attributeName("orderId")
.keyType(KeyType.HASH)
.build())
.attributeDefinitions(AttributeDefinition.builder()
.attributeName("orderId")
.attributeType(ScalarAttributeType.S)
.build())
.billingMode(BillingMode.PAY_PER_REQUEST)
.build());
// 아이템 저장
dynamoDb.putItem(PutItemRequest.builder()
.tableName("Orders")
.item(Map.of(
"orderId", AttributeValue.fromS("ORD-001"),
"product", AttributeValue.fromS("Mechanical Keyboard"),
"quantity", AttributeValue.fromN("2")))
.build());
// 아이템 조회
GetItemResponse response = dynamoDb.getItem(GetItemRequest.builder()
.tableName("Orders")
.key(Map.of("orderId", AttributeValue.fromS("ORD-001")))
.build());
System.out.println("Order: " + response.item());
}
}Java위 코드에서 핵심은 endpointOverride(FLOCI_ENDPOINT) 한 줄이다. 이 설정만으로 AWS SDK의 모든 요청이 Floci로 향한다. credentialsProvider에는 임의의 값을 넣으면 된다. 실제 AWS로 전환할 때는 endpointOverride와 StaticCredentialsProvider를 제거하고 IAM 역할 기반 인증으로 바꾸면 되므로, 코드 변경이 최소화된다.

Spring Boot 테스트에서 Floci 활용하기
Spring Boot 프로젝트에서는 테스트 프로파일을 통해 Floci 연동을 깔끔하게 분리할 수 있다.
# src/test/resources/application-test.yml
spring:
cloud:
aws:
endpoint: http://localhost:4566
region:
static: us-east-1
credentials:
access-key: test
secret-key: test
s3:
path-style-access-enabled: true # Floci는 path-style 접근이 필요YAMLSpring Cloud AWS 설정에서 endpoint를 Floci 주소로 지정하고, path-style-access-enabled를 true로 설정해야 한다. Floci는 virtual-hosted 스타일(bucket.localhost:4566)이 아닌 path 스타일(localhost:4566/bucket)로 S3 요청을 처리하기 때문이다.
Testcontainers와 조합하면 테스트 실행 시 Floci 컨테이너를 자동으로 띄우고 내릴 수 있다.
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest
@Testcontainers
class S3IntegrationTest {
// Floci 컨테이너를 테스트 시작 시 자동 실행
@Container
static GenericContainer<?> floci = new GenericContainer<>("hectorvent/floci:latest")
.withExposedPorts(4566);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
// 동적 포트 매핑 — Testcontainers가 할당한 랜덤 포트 사용
String endpoint = "http://localhost:" + floci.getMappedPort(4566);
registry.add("spring.cloud.aws.endpoint", () -> endpoint);
registry.add("spring.cloud.aws.region.static", () -> "us-east-1");
registry.add("spring.cloud.aws.credentials.access-key", () -> "test");
registry.add("spring.cloud.aws.credentials.secret-key", () -> "test");
}
// 테스트 메서드에서 S3Client를 주입받아 실제 S3 작업을 테스트한다
}Java@Container와 @DynamicPropertySource의 조합으로 Floci 컨테이너의 라이프사이클이 테스트 클래스와 동기화된다. withExposedPorts(4566)으로 포트를 노출하면 Testcontainers가 호스트 측에 랜덤 포트를 매핑하고, getMappedPort(4566)으로 그 포트 번호를 가져온다. 테스트가 끝나면 컨테이너는 자동으로 정리된다.
Testcontainers 기본 설정이 궁금하다면 Testcontainers Spring Boot 통합 테스트 가이드도 함께 참고하면 좋다.

Floci vs LocalStack, 벤치마크로 직접 비교
Floci와 LocalStack Community Edition의 성능 차이를 표로 정리하면 다음과 같다.
| 항목 | Floci (Native) | LocalStack CE | 차이 |
|---|---|---|---|
| 시작 시간 | 24ms | 3,300ms | 138배 빠름 |
| 유휴 메모리 | 13 MiB | 143 MiB | 91% 절감 |
| Docker 이미지 크기 | 90 MB | 1.0 GB | 11배 작음 |
| Lambda warm 지연 (평균) | 2ms | 10ms | 5배 빠름 |
| Lambda throughput | 289 req/s | 120 req/s | 2.4배 높음 |
| 라이선스 | MIT | 제한적 | – |
| 인증 토큰 | 불필요 | 필수 (2026.03~) | – |
CI/CD 파이프라인에서 이 차이가 쌓이면 생각보다 크다. GitHub Actions에서 매 빌드마다 LocalStack이 뜨기까지 3.3초를 기다려야 했다면, Floci는 24ms면 끝난다. 빌드당 3초 절약이면 하루 100회 빌드 기준 5분, 한 달이면 2.5시간이다.
이미지 크기 차이도 체감이 된다. 캐시가 없는 콜드 CI 환경에서 1GB를 pull하는 것과 90MB를 pull하는 것은 수십 초의 차이가 난다.
Docker 이미지 최적화에 관심이 있다면 Spring Boot Docker 최적화 가이드: 520MB 이미지를 145MB로 줄이기까지도 읽어볼 만하다.

4가지 스토리지 모드로 데이터를 내 마음대로
Floci는 용도에 따라 4가지 스토리지 백엔드를 선택할 수 있다. 환경 변수 FLOCI_STORAGE_MODE 하나로 전환한다.
environment:
FLOCI_STORAGE_MODE: hybrid # memory | persistent | hybrid | wal
FLOCI_STORAGE_PERSISTENT_PATH: /app/dataYAML각 모드의 특성은 다음과 같다.
| 모드 | 동작 방식 | 적합한 상황 |
|---|---|---|
memory | ConcurrentHashMap 기반 인메모리 저장. 컨테이너 종료 시 데이터 소멸 | 단위 테스트, CI/CD |
persistent | JSON 파일로 atomic write. 컨테이너 재시작 후에도 데이터 유지 | 로컬 개발 중 데이터 보존 |
hybrid (기본값) | 인메모리 읽기 속도 + 비동기 디스크 저장 | 대부분의 로컬 개발 |
wal | Write-Ahead Logging 방식. 고처리량 워크로드에 최적화 | 대량 데이터 처리 테스트 |
hybrid가 기본값인 이유는 간단하다. 읽기는 인메모리 속도를 쓰면서 비동기로 디스크에도 저장하기 때문에, 컨테이너가 갑자기 죽어도 데이터를 복구할 수 있다. CI/CD에서는 memory 모드로 전환하면 디스크 I/O가 없어져서 테스트가 좀 더 빨라진다.

Lambda를 진짜 Docker 컨테이너로 돌린다
Floci의 Lambda 구현은 단순한 목(mock)이 아니다. 실제 Docker 컨테이너 안에서 함수 코드를 실행한다. Node.js, Python, Java, Go, Ruby 런타임이 프로덕션 AWS Lambda와 동일한 방식으로 동작한다.
실행 흐름은 다음과 같다.
- Lambda invoke 요청이 들어오면 warm pool에서 기존 컨테이너를 확인한다
- warm 컨테이너가 없으면 런타임 이미지를 pull하고 새 컨테이너를 시작한다
- 컨테이너 내부의 bootstrap 프로세스가 Floci 내장 Runtime API Server를 폴링한다
- invocation 페이로드가 전달되고 함수가 실행된다
- 실행 완료 후 컨테이너는 warm pool로 반환되어 다음 호출에 재사용된다
이 구조 덕분에 SQS-Lambda Event Source Mapping, DynamoDB Streams 트리거 같은 이벤트 기반 통합을 로컬에서 그대로 테스트할 수 있다. Moto 같은 목 라이브러리로는 불가능한 영역이다.
# Lambda 함수 생성 (Node.js 예시)
aws lambda create-function \
--function-name my-handler \
--runtime nodejs20.x \
--role arn:aws:iam::000000000000:role/lambda-role \
--handler index.handler \
--zip-file fileb://function.zip
# Lambda 호출
aws lambda invoke \
--function-name my-handler \
--payload '{"key": "value"}' \
output.jsonShellScript위 명령어는 실제 AWS CLI의 Lambda 명령어와 완전히 동일하다. Floci가 내부적으로 Docker 컨테이너를 생성해 nodejs20.x 런타임 위에서 함수를 실행하므로, 프로덕션 환경과 동일한 동작을 로컬에서 검증할 수 있다. cold start 시간은 네이티브 빌드 기준 158ms로, LocalStack의 1,000ms 대비 6.3배 빠르다.
멀티 컨테이너 환경에서의 Floci 네트워크 설정
마이크로서비스 아키텍처에서 애플리케이션과 Floci가 별도의 Docker 컨테이너로 실행되는 경우, FLOCI_HOSTNAME 설정이 필수다.
services:
floci:
image: hectorvent/floci:latest
ports:
- "4566:4566"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
FLOCI_HOSTNAME: floci # 응답 URL에 포함될 호스트명
my-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:
- flociYAMLFLOCI_HOSTNAME을 설정하지 않으면 SQS 큐 URL이 http://localhost:4566/...으로 반환되는데, 별도 컨테이너에서는 localhost가 자기 자신을 가리키므로 통신이 실패한다. FLOCI_HOSTNAME: floci로 설정하면 큐 URL이 http://floci:4566/...으로 반환되어 Docker 네트워크 내에서 정상적으로 resolve된다.
알아두어야 할 제한 사항
Floci는 2026년 3월 22일에 처음 공개된 신생 프로젝트다. GitHub 스타 2,600개 이상, 포크 132개를 기록하며 빠르게 성장하고 있지만, 몇 가지 제한 사항은 인지하고 사용해야 한다.
- LocalStack Pro가 80개 이상의 서비스를 지원하는 반면, Floci는 25개다. Route53, CloudFront, ECS, EKS는 아직 없다.
- S3 단일 업로드 최대 크기가 27MB로 제한되어 있다. 대용량 파일은 multipart upload를 써야 한다.
- Lambda 런타임 중 Ruby는 아직 지원하지 않는다.
- 일부 SQS 메시지 라우팅 시나리오에서 알려진 버그가 있다 (GitHub Issues 참고).
S3, DynamoDB, SQS, Lambda, API Gateway 중심의 일반적인 백엔드 개발과 통합 테스트에서는 부족함이 없다. Quarkus 프로젝트에서도 Floci를 Dev Services 대안으로 검토하는 이슈가 올라와 있다.
마치며
지금까지 Floci의 설치부터 Spring Boot 연동, 성능 비교, 스토리지 모드, Lambda 실행 방식까지 정리해 보았다. 아직 공개된 지 2주 남짓된 신생 프로젝트라 안정성 면에서 지켜볼 부분이 있지만, 기본적인 S3/DynamoDB/SQS/Lambda 워크플로우에서는 LocalStack을 충분히 대체할 수 있다. 무엇보다 인증 토큰 없이 CI/CD에서 제한 없이 쓸 수 있다는 점이 실무에서 가장 반갑다. docker-compose.yml에서 이미지 이름 하나만 바꾸면 되니, LocalStack 인증 토큰 때문에 짜증났던 분이라면 한 번 돌려보길 권한다.
