floci-io/floci
GitHub: floci-io/floci
Floci 是一个免费、开源、超轻量的 AWS 本地模拟器,通过真实 Docker 容器提供高保真服务模拟,可无缝替代 LocalStack。
Stars: 5405 | Forks: 381
以 floccus 命名 —— 一种看起来极像爆米花的云状形态。
免费、开源的本地 AWS 模拟器。无需账号。没有功能限制。只需 docker compose up。
加入 Slack 社区提问、分享反馈,并与其他贡献者和用户讨论 Floci。您也可以在 GitHub Discussions 中开启任何话题 —— 功能创意、兼容性问题、设计权衡、大胆提案或半成品的想法都非常欢迎。没有哪个想法会因为太小、太早或太凭一时脑热而不值得开启一场好的讨论。
## 为什么选择 Floci?
| | Floci | LocalStack Community |
|---|---|---|
| 需要 Auth token | 否 | 是 (自 2026 年 3 月起) |
| 安全更新 | 是 | 冻结 |
| 启动时间 | **~24 ms** | ~3.3 s |
| 空闲内存 | **~13 MiB** | ~143 MiB |
| Docker 镜像大小 | **~90 MB** | ~1.0 GB |
| 许可证 | **MIT** | 受限 |
| API Gateway v2 / HTTP API | ✅ | ❌ |
| Cognito | ✅ | ❌ |
| ElastiCache (Redis + IAM auth) | ✅ | ❌ |
| RDS (PostgreSQL + MySQL + IAM auth) | ✅ | ❌ |
| MSK (Kafka + Redpanda) | ✅ | ❌ |
| Athena (通过 DuckDB sidecar 执行真实 SQL + Glue 视图) | ✅ | ❌ |
| Glue Data Catalog + Schema Registry | ✅ | ❌ |
| Data Firehose (NDJSON 投递) | ✅ | ❌ |
| S3 Object Lock (COMPLIANCE / GOVERNANCE) | ✅ | ⚠️ 部分 |
| DynamoDB Streams | ✅ | ⚠️ 部分 |
| IAM (用户、角色、策略、组) | ✅ | ⚠️ 部分 |
| STS (全部 7 个操作) | ✅ | ⚠️ 部分 |
| Kinesis (流、分片、fan-out) | ✅ | ⚠️ 部分 |
| KMS (签名、验证、重新加密) | ✅ | ⚠️ 部分 |
| ECS (集群、服务、任务) | ✅ | ❌ |
| EKS (集群、模拟 + 真实 k3s) | ✅ | ❌ |
| EC2 (真实 Docker 实例、IMDS、SSH、UserData) | ✅ | ❌ |
| CodeBuild (真实 Docker 构建执行、S3 artifacts、CloudWatch 日志) | ✅ | ❌ |
| CodeDeploy (Lambda 流量切换、生命周期钩子、自动回滚) | ✅ | ❌ |
| Auto Scaling (组、启动配置、reconciler、ELB v2 集成) | ✅ | ❌ |
| SSM Run Command (SendCommand + 通过 ec2messages 进行真实 agent 轮询) | ✅ | ❌ |
| Transfer Family (SFTP 服务器管理、用户、SSH 密钥) | ✅ | ❌ |
| 原生二进制文件 | ✅ ~40 MB | ❌ |
**广泛的 AWS 覆盖范围。永久免费。**
## 从 LocalStack 迁移
Floci 是 LocalStack Community 的直接替代品。端口 (`4566`)、凭证以及所有 AWS SDK 和 CLI 调用都无需修改即可正常工作 —— 只需替换镜像即可。
```
# 之前
image: localstack/localstack
# 之后 — 无 init 脚本,或不调用 aws / boto3 的脚本
image: floci/floci:latest
# 之后 — 使用 aws CLI 或 boto3 的 init 脚本
image: floci/floci:latest-compat # includes Python 3, AWS CLI, boto3 pre-configured
```
**LocalStack 环境变量会被自动转换** —— 无需重命名:
| LocalStack | Floci 等效项 |
|---|---|
| `LOCALSTACK_HOST` | `FLOCI_HOSTNAME` |
| `PERSISTENCE=1` | `FLOCI_STORAGE_MODE=persistent` |
| `LAMBDA_DOCKER_NETWORK` | `FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK` |
| `LAMBDA_REMOVE_CONTAINERS=1` | `FLOCI_SERVICES_LAMBDA_EPHEMERAL=true` |
| `DEBUG=1` | `QUARKUS_LOG_LEVEL=DEBUG` |
挂载在 `/etc/localstack/init/` 下的初始化脚本无需修改即可运行。`/_localstack/init` 和 `/_localstack/health` 端点依然提供服务。设置 `LOCALSTACK_PARITY=false` 可退出自动转换。
→ [完整迁移指南](https://floci.io/floci/getting-started/migrate-from-localstack/)
## 架构概览
```
flowchart LR
Client["☁️ AWS SDK / CLI"]
subgraph Floci ["Floci — port 4566"]
Router["HTTP Router\n(JAX-RS / Vert.x)"]
subgraph Stateless ["Stateless Services"]
A["SSM · SQS · SNS\nIAM · STS · KMS\nSecrets Manager · SES\nCognito · Kinesis\nEventBridge · Scheduler · AppConfig\nCloudWatch · Step Functions\nCloudFormation · ACM\nAPI Gateway · ELB v2 · Auto Scaling\nCodeDeploy · Backup · Bedrock Runtime · Route53 · Transfer"]
end
subgraph Stateful ["Stateful Services"]
B["S3 · DynamoDB\nDynamoDB Streams"]
end
subgraph Containers ["Container Services 🐳"]
C["Lambda\nElastiCache\nRDS\nECS\nEC2\nMSK\nEKS\nOpenSearch\nCodeBuild"]
D["Athena ➜ floci-duck\n(DuckDB sidecar)"]
end
Router --> Stateless
Router --> Stateful
Router --> Containers
Stateless & Stateful --> Store[("StorageBackend\nmemory · hybrid\npersistent · wal")]
end
Docker["🐳 Docker Engine"]
Client -->|"HTTP :4566\nAWS wire protocol"| Router
Containers -->|"Docker API\n+ IAM / SigV4 auth"| Docker
```
## 真实 Docker 集成
与仅使用模拟的模拟器不同,对于进程内模拟会影响保真度的服务(有状态数据库、高连接协议以及需要原生执行的 runtime),Floci 会运行**真实的 Docker 容器**。其结果是针对实际引擎的线路兼容行为,而非简化的近似。
| 服务 | 默认 Docker 镜像 | 真实内容 |
|---|---|---|
| **Lambda** | `public.ecr.aws/lambda/
` | AWS runtime 环境、执行模型、热容器池 |
| **ElastiCache** | `valkey/valkey:8` | 完整的 Redis/Valkey 协议,基于 ACL 的 IAM auth,SigV4 验证 |
| **RDS (PostgreSQL)** | `postgres:16-alpine` | 真实的 PostgreSQL 引擎,通过 token 进行 IAM auth,兼容 JDBC |
| **RDS (MySQL / Aurora)** | `mysql:8.0` | 真实的 MySQL 引擎,IAM auth,兼容 JDBC |
| **RDS (MariaDB)** | `mariadb:11` | 真实的 MariaDB 引擎,IAM auth,兼容 JDBC |
| **MSK** | `redpandadata/redpanda:latest` | 通过 Redpanda 实现的真实兼容 Kafka 的代理 |
| **EC2** | 映射的 AMI (例如 `public.ecr.aws/amazonlinux/amazonlinux:2023`) | 真实的 Linux 容器;SSH 密钥注入;UserData 执行;支持 IMDSv1+IMDSv2 的 IMDS 及 IAM 凭证分发 |
| **ECS** | 在任务定义中由用户指定 | 真实的容器生命周期 —— 启动、停止、健康检查 |
| **EKS** | `rancher/k3s:latest` | 实时 Kubernetes API 服务器 (k3s),完整的 kubeconfig |
| **CodeBuild** | 由用户指定的环境镜像 (例如 `public.ecr.aws/codebuild/amazonlinux2-x86_64-standard:5.0`) | 真实的 buildspec 执行 —— 容器中的 install/pre_build/build/post_build 阶段;S3 artifact 上传;CloudWatch 日志流式传输 |
| **OpenSearch** | `opensearchproject/opensearch:2` | 带有 REST API 的完整 OpenSearch 引擎 |
| **ECR** | `registry:2` | 真实的兼容 OCI 的 registry —— `docker push` / `docker pull` 可原生使用 |
### Lambda runtimes
Floci 将每个 Lambda runtime 解析为相应的 [AWS 公共 ECR 镜像](https://gallery.ecr.aws/lambda):
| Runtime | 镜像 |
|---|---|
| `java25` · `java21` · `java17` · `java11` · `java8.al2` · `java8` | `public.ecr.aws/lambda/java:` |
| `python3.14` · `python3.13` · `python3.12` · `python3.11` · `python3.10` · `python3.9` | `public.ecr.aws/lambda/python:` |
| `nodejs24.x` · `nodejs22.x` · `nodejs20.x` · `nodejs18.x` · `nodejs16.x` | `public.ecr.aws/lambda/nodejs:` |
| `ruby3.4` · `ruby3.3` · `ruby3.2` | `public.ecr.aws/lambda/ruby:` |
| `dotnet10` · `dotnet9` · `dotnet8` · `dotnet6` | `public.ecr.aws/lambda/dotnet:` |
| `go1.x` | `public.ecr.aws/lambda/go:1` |
| `provided.al2023` · `provided.al2` · `provided` | `public.ecr.aws/lambda/provided:` |
容器镜像函数 (package type `Image`) 会直接传递 `ImageUri`,其中 ECR 存储库 URI 会自动重写为本地 Floci ECR 端点。
### 系统要求
Docker 支持的服务需要 Docker socket 可访问:
```
docker run -d --name floci \
-p 4566:4566 \
-v /var/run/docker.sock:/var/run/docker.sock \
-u root \
floci/floci:latest
```
在 Docker Compose 中,除了任何其他挂载之外,还要添加 socket 卷。
### 覆盖默认镜像
所有默认镜像都可通过环境变量进行配置,适用于锁定版本或使用本地镜像源:
| 变量 | 默认值 |
|---|---|
| `FLOCI_SERVICES_ELASTICACHE_DEFAULT_IMAGE` | `valkey/valkey:8` |
| `FLOCI_SERVICES_RDS_DEFAULT_POSTGRES_IMAGE` | `postgres:16-alpine` |
| `FLOCI_SERVICES_RDS_DEFAULT_MYSQL_IMAGE` | `mysql:8.0` |
| `FLOCI_SERVICES_RDS_DEFAULT_MARIADB_IMAGE` | `mariadb:11` |
| `FLOCI_SERVICES_MSK_DEFAULT_IMAGE` | `redpandadata/redpanda:latest` |
| `FLOCI_SERVICES_OPENSEARCH_DEFAULT_IMAGE` | `opensearchproject/opensearch:2` |
| `FLOCI_SERVICES_EKS_DEFAULT_IMAGE` | `rancher/k3s:latest` |
| `FLOCI_SERVICES_ECR_REGISTRY_IMAGE` | `registry:2` |
| `FLOCI_ECR_BASE_URI` | `public.ecr.aws` (Lambda runtime 基础路径) |
## 支持的服务
| 服务 | 工作原理 | 显著特性 |
|---|---|---|
| **SSM Parameter Store** | 进程内 | 版本历史、标签、SecureString、 tagging |
| **SSM Run Command** | 进程内 | `SendCommand`、`GetCommandInvocation`、`ListCommands`、`CancelCommand`;`DescribeInstanceInformation`;`ec2messages` 轮询协议,以便在 EC2 容器内运行的真实 `amazon-ssm-agent` 能够注册、接收命令并报告输出 |
| **SQS** | 进程内 | Standard 和 FIFO,DLQ,visibility timeout,批量操作,tagging |
| **SNS** | 进程内 | 主题、订阅、SQS / Lambda / HTTP 投递、tagging |
| **S3** | 进程内 | 版本控制、分段上传、预签名 URL、Object Lock、事件通知 |
| **DynamoDB** | 进程内 | GSI / LSI,Query,Scan,TTL,事务,批量操作 |
| **DynamoDB Streams** | 进程内 | 分片迭代器、记录、Lambda ESM 触发器 |
| **Lambda** | **真实 Docker 容器** | 热池、别名、Function URLs、SQS / Kinesis / DDB Streams ESM |
| **API Gateway REST** | 进程内 | 资源、方法、阶段、Lambda proxy、MOCK 集成、AWS 集成 |
| **API Gateway v2 (HTTP)** | 进程内 | 路由、集成、JWT authorizers、阶段 |
| **IAM** | 进程内 | 用户、角色、组、策略、实例配置文件、访问密钥 |
| **STS** | 进程内 | AssumeRole、WebIdentity、SAML、GetFederationToken、GetSessionToken |
| **Cognito** | 进程内 | User pools、app clients、auth flows、JWKS / OpenID well-known 端点 |
| **KMS** | 进程内 | 加密/解密、签名/验证、数据密钥、别名 |
| **Kinesis** | 进程内 | 流、分片、增强型 fan-out、拆分/合并 |
| **Secrets Manager** | 进程内 | 版本控制、资源策略、tagging |
| **Step Functions** | 进程内 | ASL 执行、任务 token、执行历史 |
| **CloudFormation** | 进程内 | Stacks、change sets、资源调配 |
| **EventBridge** | 进程内 | 自定义总线、规则、目标 (SQ / SNS / Lambda) |
| **EventBridge Scheduler** | 进程内 | 调度组、调度计划、灵活时间窗口、重试策略、dead-letter queues |
| **CloudWatch Logs** | 进程内 | 日志组、日志流、数据摄取、过滤 |
| **CloudWatch Metrics** | 进程内 | 自定义指标、统计信息、告警 |
| **ElastiCache** | **真实 Docker 容器** | Redis / Valkey,IAM auth,SigV4 验证 |
| **RDS** | **真实 Docker 容器** | PostgreSQL 和 MySQL,IAM auth,兼容 JDBC |
| **MSK** | **真实 Docker 容器** | 通过 Redpanda 编排实现兼容 Kafka |
| **Athena** | 进程内 + **DuckDB sidecar** | 真实的 SQL 执行;S3 数据上由 Glue 支持的视图;根据 SerDe 推断 `read_parquet` / `read_json_auto` / `read_csv_auto` |
| **Glue** | 进程内 | Data Catalog;用于 Avro / JSON Schema / Protobuf 的 Schema Registry;表在查询时被 Athena 作为 DuckDB 视图消费 |
| **Data Firehose** | 进程内 | 流数据投递;记录作为 NDJSON 刷入 S3 |
| **ECS** | **真实 Docker 容器** | 集群、任务定义、任务、服务、容量提供程序、任务集 |
| **EC2** | **真实 Docker 容器** | `RunInstances` 启动真实的 Docker 容器;SSH 密钥注入;UserData 执行;IMDS (IMDSv1+IMDSv2, 端口 9169) 及 IAM 凭证分发;VPC、子网、安全组、AMI、密钥对、Internet 网关、路由表、Elastic IP、标签 |
| **ACM** | 进程内 | 证书颁发、验证生命周期 |
| **ECR** | 进程内 + **真实 OCI registry** | 存储库、通过标准 `docker` 进行镜像推/拉、基于镜像的 Lambda 函数 |
| **SES** | 进程内 | 发送电子邮件/原始电子邮件、身份验证、DKIM 属性、带有 `{{var}}` 替换的电子邮件模板 |
| **SES v2 (HTTP)** | 进程内 | REST JSON API、身份、DKIM、feedback 属性、账号发送、带有 `{{var}}` 替换的电子邮件模板 |
| **OpenSearch** | **真实 Docker 容器** | Domain CRUD、标签、版本、实例类型、升级桩 |
| **AppConfig** | 进程内 | 应用程序、环境、配置文件、托管配置版本、部署 |
| **AppConfigData** | 进程内 | 配置会话、动态配置检索 |
| **Bedrock Runtime** | 进程内 (桩) | 用于本地开发的虚拟 Converse 和 InvokeModel 响应;流式传输返回 501 |
| **EKS** | **真实 Docker 容器** (提供模拟模式) | 集群、tagging;真实模式为每个集群启动带有实时 Kubernetes API 服务器的 k3s |
| **ELB v2** | 进程内 | Application 和 Network Load Balancer、目标组、监听器、基于路径/主机的路由规则、Lambda 目标 (ALB→Lambda 事件格式)、标签 |
| **CodeBuild** | 进程内 + **真实 Docker 容器** | 项目、报告组、源凭证;`StartBuild` 运行真实的 Docker 容器,将日志流式传输到 CloudWatch,通过 `docker cp` 将 artifacts 上传到 S3 (支持 Docker-in-Docker) |
| **CodeDeploy** | 进程内 + **Lambda 流量切换** | 应用程序、部署组、部署配置;预置了 17 个 `CodeDeployDefault.*` 内置项;`CreateDeployment` 切换 Lambda 别名 `RoutingConfig` 权重,调用生命周期钩子,失败时自动回滚 |
| **Auto Scaling** | 进程内 + **后台 reconciler** | 启动配置、具有 min/max/desired capacity 的 Auto Scaling 组;后台循环 (10 秒) 调用 `RunInstances` / `TerminateInstances` 以满足期望容量;生命周期钩子、扩展策略、ELB v2 目标组自动注册 |
| **AWS Backup** | 进程内 | 备份库、带有规则的备份计划、资源选择、带有模拟生命周期 (CREATED → RUNNING → COMPLETED) 的按需作业、恢复点、tagging |
| **Route53** | 进程内 | 带有自动创建的 SOA + NS 记录的托管区域、资源记录集 (CREATE/UPSERT/DELETE 附带原子验证)、变更跟踪 (始终为 INSYNC)、健康检查以及每资源 tagging |
| **Transfer Family** | 进程内 | 服务器生命周期 (`CreateServer` / `DeleteServer` / `StartServer` / `StopServer` / `UpdateServer`)、用户管理、SSH 公钥导入和 tagging |
| **Textract** | 进程内 (桩) | 兼容所有操作的 API 桩;具有真实结构和元数据的虚拟块数据;带有即时 SUCCEEDED 状态的异步作业模拟 |
**支持 46 个 AWS 服务。**
## 持久化与存储模式
Floci 具有灵活的存储架构,旨在平衡开发者生产力、性能和数据持久性。您可以通过 `FLOCI_STORAGE_MODE` 全局配置存储模式,或者为特定服务覆盖该模式。
| 模式 | 行为 | 最适用于... | 持久性 |
|:---:|---|---|:---:|
| **`memory`** | **(默认)** 完全在内存中。容器停止时数据丢失。 | 速度、临时测试、CI pipelines。 | ❌ 无 |
| **`persistent`** | 数据在启动时加载,在优雅关闭时刷入磁盘。 | 带有状态保留的简单本地开发。 | ⚠️ 中等 |
| **`hybrid`** | 内存性能,带有定期的异步刷盘 (每 5 秒)。 | 速度与安全的完美平衡。 | ✅ 良好 |
| **`wal`** | 预写式日志。每次修改在响应前都会记录到磁盘。 | 关键状态的最大持久性。 | 💎 最高 |
欲了解更多详情,请访问 [存储配置文档](https://floci.io/floci/configuration/storage/)。
## 多账号隔离
Floci 支持完整的按账号资源隔离,且无需额外配置。如果您的 AWS access key ID 恰好是 12 位数字,Floci 将直接将其作为账号 ID —— 一个账号创建的资源对另一个账号完全不可见。
```
# 两个账户,完全隔离 — 相同队列名,独立 namespaces
AWS_ACCESS_KEY_ID=111111111111 aws sqs create-queue --queue-name orders
AWS_ACCESS_KEY_ID=222222222222 aws sqs create-queue --queue-name orders
```
任何其他密钥格式 (例如 `test`、`AKIA…`) 将回退到 `FLOCI_DEFAULT_ACCOUNT_ID` (默认为 `000000000000`) —— 标准的单账号设置。
→ [多账号隔离文档](https://floci.io/floci/configuration/multi-account/)
## 快速开始
```
# docker-compose.yml
services:
floci:
image: floci/floci:latest
ports:
- "4566:4566"
volumes:
# Local directory bind mount (default)
- ./data:/app/data
# OR named volume (optional):
# - floci-data:/app/data
#volumes:
# floci-data:
```
```
docker compose up
```
或者直接使用 Docker 运行 Floci:
```
docker run -d --name floci \
-p 4566:4566 \
-v /var/run/docker.sock:/var/run/docker.sock \
-e FLOCI_DEFAULT_REGION=us-east-1 \
-u root \
floci/floci:latest
```
所有服务均可在 `http://localhost:4566` 获取。可以使用任何 AWS region —— 凭证可以是任意值。
```
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
# 试一试
aws s3 mb s3://my-bucket
aws sqs create-queue --queue-name my-queue
aws dynamodb list-tables
```
## SDK 集成
将您现有的 AWS SDK 指向 `http://localhost:4566` —— 无需进行其他更改。
```
// Java (AWS SDK v2)
var client = DynamoDbClient.builder()
.endpointOverride(URI.create("http://localhost:4566"))
.region(Region.US_EAST_1)
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("test", "test")))
.build();
client.createTable(b -> b
.tableName("demo-table")
.billingMode(BillingMode.PAY_PER_REQUEST)
.attributeDefinitions(
AttributeDefinition.builder().attributeName("pk").attributeType(ScalarAttributeType.S).build())
.keySchema(
KeySchemaElement.builder().attributeName("pk").keyType(KeyType.HASH).build()));
client.putItem(b -> b
.tableName("demo-table")
.item(Map.of("pk", AttributeValue.fromS("item-1"))));
System.out.println(client.listTables().tableNames());
```
```
# Python (boto3)
import boto3
client = boto3.client("ssm",
endpoint_url="http://localhost:4566",
region_name="us-east-1",
aws_access_key_id="test",
aws_secret_access_key="test")
client.put_parameter(
Name="/demo/app/message",
Value="hello from floci",
Type="String",
Overwrite=True,
)
response = client.get_parameter(Name="/demo/app/message")
print(response["Parameter"]["Value"])
```
```
// consumer.mjs
// Node.js (AWS SDK v3)
import {DeleteMessageCommand, ReceiveMessageCommand, SQSClient,} from "@aws-sdk/client-sqs";
const client = new SQSClient({
endpoint: "http://localhost:4566",
region: "us-east-1",
credentials: {accessKeyId: "test", secretAccessKey: "test"},
});
const QUEUE_URL = "http://localhost:4566/000000000000/demo-queue";
const response = await client.send(
new ReceiveMessageCommand({
QueueUrl: QUEUE_URL,
MaxNumberOfMessages: 1,
WaitTimeSeconds: 5,
}),
);
if (response.Messages) {
for (const msg of response.Messages) {
console.log("Message received:", msg.Body);
await client.send(
new DeleteMessageCommand({
QueueUrl: QUEUE_URL,
ReceiptHandle: msg.ReceiptHandle,
}),
);
}
}
```
```
// producer.mjs
// Node.js (AWS SDK v3)
import {SendMessageCommand, SQSClient} from "@aws-sdk/client-sqs";
const client = new SQSClient({
endpoint: "http://localhost:4566",
region: "us-east-1",
credentials: {accessKeyId: "test", secretAccessKey: "test"},
});
const QUEUE_URL = "http://localhost:4566/000000000000/demo-queue";
await client.send(
new SendMessageCommand({
QueueUrl: QUEUE_URL,
MessageBody: "hello from producer",
}),
);
console.log("Message sent");
```
```
// Go (AWS SDK v2)
package main
import (
"context"
"fmt"
"log"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider("test", "test", ""),
),
config.WithBaseEndpoint("http://localhost:4566"),
)
if err != nil {
log.Fatal(err)
}
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.UsePathStyle = true
})
_, err = client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
Bucket: aws.String("demo-bucket"),
})
if err != nil {
log.Fatal(err)
}
_, err = client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String("demo-bucket"),
Key: aws.String("demo.txt"),
Body: strings.NewReader("hello from floci"),
})
if err != nil {
log.Fatal(err)
}
out, err := client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
Bucket: aws.String("demo-bucket"),
})
if err != nil {
log.Fatal(err)
}
if len(out.Contents) > 0 {
fmt.Println(*out.Contents[0].Key)
}
}
```
```
// Rust (AWS SDK)
use aws_sdk_secretsmanager::config::{Credentials, Region};
use aws_sdk_secretsmanager::Client;
#[tokio::main]
async fn main() -> Result<(), Box> {
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
.region(Region::new("us-east-1"))
.credentials_provider(Credentials::new("test", "test", None, None, "floci"))
.endpoint_url("http://localhost:4566")
.load()
.await;
let client = Client::new(&config);
client
.create_secret()
.name("demo/secret")
.secret_string("hello from floci")
.send()
.await?;
let secret = client
.get_secret_value()
.secret_id("demo/secret")
.send()
.await?;
println!("{}", secret.secret_string().unwrap());
Ok(())
}
```
```
# Bash (AWS CLI)
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
tmp_file="$(mktemp)"
echo "hello from floci" > "$tmp_file"
aws --endpoint-url http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url http://localhost:4566 s3 cp "$tmp_file" s3://my-bucket/demo.txt
aws --endpoint-url http://localhost:4566 s3 ls s3://my-bucket
# 清理
aws --endpoint-url http://localhost:4566 s3 rm s3://my-bucket/demo.txt
rm -f "$tmp_file"
```
## Testcontainers
Floci 拥有一流的 Testcontainers 模块,因此您可以从测试中启动真实的 Floci 实例,无需任何手动设置 —— 没有运行中的 daemon,没有共享状态,也没有端口冲突。
| 语言 | 包 | 最新版 | Registry | 源码 |
|---|---|---|---|---|
| Java | `io.floci:testcontainers-floci` | `1.4.0` | [Maven Central](https://mvnrepository.com/artifact/io.floci/testcontainers-floci) | [GitHub](https://github.com/floci-io/testcontainers-floci) |
| Node.js | `@floci/testcontainers` | `0.1.0` | [npm](https://www.npmjs.com/package/@floci/testcontainers) | [GitHub](https://github.com/floci-io/testcontainers-floci-node) |
| Python | `testcontainers-floci` | `0.1.1` | [PyPI](https://pypi.org/project/testcontainers-floci/) | [GitHub](https://github.com/floci-io/testcontainers-floci-python) |
| Go | — | 🚧 开发中 | — | [GitHub](https://github.com/floci-io/testcontainers-floci-go) |
### Java
添加依赖项 (Testcontainers 1.x / Spring Boot 3.x):
```
io.floci
testcontainers-floci
1.4.0
test
```
对于 Testcontainers 2.x / Spring Boot 4.x,请使用版本 `2.5.0`。
在 JUnit 5 中的基本用法:
```
@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"));
}
}
```
**Spring Boot** —— 添加 `spring-boot-testcontainers-floci` 并使用 `@ServiceConnection` 实现零配置自动装配:
```
@SpringBootTest
@Testcontainers
class AppIntegrationTest {
@Container
@ServiceConnection
static FlociContainer floci = new FlociContainer();
@Autowired
S3Client s3;
@Test
void shouldCreateBucket() {
s3.createBucket(b -> b.bucket("my-bucket"));
assertThat(s3.listBuckets().buckets())
.anyMatch(b -> b.name().equals("my-bucket"));
}
}
```
### Node.js / TypeScript
```
npm install --save-dev @floci/testcontainers
```
```
import { FlociContainer } from "@floci/testcontainers";
import { S3Client, CreateBucketCommand, ListBucketsCommand } from "@aws-sdk/client-s3";
describe("S3", () => {
let floci: FlociContainer;
beforeAll(async () => {
floci = await new FlociContainer().start();
});
afterAll(async () => {
await floci.stop();
});
it("should create and list a bucket", async () => {
const s3 = new S3Client({
endpoint: floci.getEndpoint(),
region: floci.getRegion(),
credentials: {
accessKeyId: floci.getAccessKey(),
secretAccessKey: floci.getSecretKey(),
},
forcePathStyle: true,
});
await s3.send(new CreateBucketCommand({ Bucket: "my-bucket" }));
const { Buckets } = await s3.send(new ListBucketsCommand({}));
expect(Buckets?.some(b => b.Name === "my-bucket")).toBe(true);
});
});
```
### Python
```
pip install testcontainers-floci
```
```
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="my-bucket")
buckets = s3.list_buckets()["Buckets"]
assert any(b["Name"] == "my-bucket" for b in buckets)
```
Pytest fixture 风格:
```
import pytest
import boto3
from testcontainers_floci import FlociContainer
@pytest.fixture(scope="session")
def floci():
with FlociContainer() as container:
yield container
def test_s3_create_bucket(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="my-bucket")
buckets = s3.list_buckets()["Buckets"]
assert any(b["Name"] == "my-bucket" for b in buckets)
```
### Go
Go 支持正在开发中。请在 [testcontainers-floci-go](https://github.com/floci-io/testcontainers-floci-go) 追踪进度。
## 兼容性测试
此目录提供了跨多个 SDK 和工具场景的 Floci 专用兼容性测试套件,是在端到端验证集成行为时的推荐起点。
可用的兼容性测试模块:
| 模块 | 语言 / 工具 | SDK / 客户端 / 版本 | 测试 |
|---|---|---|---:|
| `sdk-test-java` | Java 17 | AWS SDK for Java v2 | 889 |
| `sdk-test-node` | Node.js | AWS SDK for JavaScript v3 | 360 |
| `sdk-test-python` | Python 3 | boto3 | 264 |
| `sdk-test-go` | Go | AWS SDK for Go v2 | 136 |
| `sdk-test-awscli` | Bash | AWS CLI v2 | 145 |
| `sdk-test-rust` | Rust | AWS SDK for Rust | 86 |
| `compat-terraform` | Terraform | v1.10+ | 14 |
| `compat-opentofu` | OpenTofu | v1.9+ | 14 |
| `compat-cdk` | AWS CDK | v2+ | 17 |
**跨 6 个 SDK 和 3 个 IaC 工具的 1,850 多个自动化兼容性测试。**
## 镜像标签
每个标签结合了两个选择:**变体** (包含的内容) 和 **渠道** (稳定程度)。
| | Standard | Compat (+ AWS CLI + boto3) |
|---|---|---|
| **Release (latest)** | `latest` ✅ | `latest-compat` |
| **Release (pinned)** | `x.y.z` | `x.y.z-compat` |
| **Nightly (浮动)** | `nightly` | `nightly-compat` |
| **Nightly (日期)** | `nightly-mmddyyyy` | `nightly-mmddyyyy-compat` |
- **Standard** —— GraalVM 原生二进制文件。约 24 ms 启动,约 40 MB 镜像,约 13 MiB 空闲内存。
- **Compat** —— 在 Standard 镜像基础上扩展了 Python 3、AWS CLI 和 boto3。启动速度和内存占用相同,但镜像体积更大。
- **Release** —— 在每个稳定版本标签上发布。
- **Nightly** —— 每晚 22:00 CT 从 `main` 分支构建。带有日期的标签 (例如 `nightly-05022026`) 是固定的;`nightly` 始终指向最新版本。
```
# 推荐
image: floci/floci:latest
# 使用 AWS CLI + boto3
image: floci/floci:latest-compat
# 固定版本
image: floci/floci:1.5.11
# 追踪 main
image: floci/floci:nightly
```
## 配置
所有设置都可以通过环境变量 (`FLOCI_` 前缀) 进行覆盖。
| 变量 | 默认值 | 描述 |
|---|---|-------------------------------------------------------------------------------------|
| `FLOCI_PORT` | `4566` | Floci API 暴露的端口 |
| `FLOCI_DEFAULT_REGION` | `us-east-1` | 默认 AWS region |
| `FLOCI_DEFAULT_ACCOUNT_ID` | `000000000000` | 默认 AWS 账号 ID |
| `FLOCI_BASE_URL` | `http://localhost:4566` | Floci 返回服务 URL 时使用的基础 URL (例如 SQS QueueUrl) |
| `FLOCI_HOSTNAME` | *(未设置)* | 当 Floci 在 Docker Compose 内部运行时,在返回的 URL 中使用的 Hostname |
| `FLOCI_STORAGE_MODE` | `memory` | 控制跨运行的数据存储方式:`memory` · `persistent` · `hybrid` · `wal` |
| `FLOCI_STORAGE_PERSISTENT_PATH` | `./data` | 用于持久化目录 |
| `FLOCI_ECR_BASE_URI` | `public.ecr.aws` | 拉取容器镜像 (例如 Lambda) 时使用的 AWS ECR 基础 URI |
* 完整参考:[配置文档](https://floci.io/floci/configuration/application-yml/)
* 按服务覆盖存储:[存储文档](https://floci.io/floci/configuration/storage/#per-service-storage-overrides)
**多容器 Docker Compose:** 当您的应用程序在独立于 Floci 的单独容器中运行时,请将 `FLOCI_HOSTNAME` 设置为 Floci 的服务名称,以便返回的 URL (例如 SQS QueueUrl) 能够正确解析:
```
services:
floci:
image: floci/floci:latest
ports:
- "4566:4566"
environment:
- FLOCI_HOSTNAME=floci # URLs will use http://floci:4566/...
my-app:
environment:
- AWS_ENDPOINT_URL=http://floci:4566
depends_on:
- floci
```
如果不进行此设置,SQS 在 QueueUrl 响应中将返回 `http://localhost:4566/...`,这将解析到错误的容器。
## Star 历史
[](https://star-history.com/#floci-io/floci&Date)
## 贡献者
## 许可证
MIT —— 随意使用。标签:AWS, AWS模拟器, Docker, Docker Compose, DPI, JS文件枚举, Lambda, LangChain, LocalStack替代品, MIT协议, NIDS, S3, 云服务测试, 云计算, 域名枚举, 安全防御评估, 容器化, 开发测试工具, 开源, 无服务器, 本地开发环境, 本地模拟, 版权保护, 规则引擎, 请求拦截, 轻量级