second first commit
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
# application.yml Reference
|
||||
|
||||
!!! note "Source builds only"
|
||||
This page is for users who build Floci from source or mount a custom `application.yml` into the container. **If you run the published Docker image, you don't need this file** — all settings are configured through `FLOCI_*` environment variables. See the [Environment Variables Reference](../environment-variables.md) for the complete list.
|
||||
|
||||
All settings can be provided as YAML (in `src/main/resources/application.yml` or mounted as a config file) or overridden via environment variables using the `FLOCI_` prefix with dots and dashes replaced by underscores.
|
||||
|
||||
## URL configuration
|
||||
|
||||
Floci generates absolute URLs for certain response fields (SQS queue URLs, SNS
|
||||
subscription endpoints, pre-signed S3 URLs). Two settings control the hostname
|
||||
embedded in those URLs:
|
||||
|
||||
| Setting | Env variable | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `floci.base-url` | `FLOCI_BASE_URL` | `http://localhost:4566` | Full base URL used to build response URLs. Change the scheme, host, and port together. |
|
||||
| `floci.hostname` | `FLOCI_HOSTNAME` | _(none)_ | Override only the hostname in `base-url`. Useful in Docker Compose where `localhost` is unreachable from other containers. |
|
||||
|
||||
When `floci.hostname` is set it replaces just the host portion of `base-url`,
|
||||
leaving the scheme and port unchanged. Setting `FLOCI_HOSTNAME: floci` is
|
||||
equivalent to changing `base-url` from `http://localhost:4566` to
|
||||
`http://floci:4566`.
|
||||
|
||||
**Example — Docker Compose multi-container setup:**
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
FLOCI_HOSTNAME: floci # matches the compose service name
|
||||
```
|
||||
|
||||
See [Docker Compose — Multi-container networking](../docker-compose.md#multi-container-networking) for a full example.
|
||||
|
||||
## Full Reference
|
||||
|
||||
The block below mirrors `src/main/resources/application.yml`, it's the effective set of keys Floci ships with. Some supported keys are omitted here (for example `floci.init-hooks.*`) but can still be provided via YAML or environment variables.
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
base-url: "http://localhost:4566" # Used to build response URLs (SQS QueueUrl, SNS endpoints, etc.)
|
||||
# hostname: "" # When set, overrides the host in base-url for multi-container Docker
|
||||
default-region: us-east-1
|
||||
default-account-id: "000000000000"
|
||||
|
||||
storage:
|
||||
mode: memory # memory | persistent | hybrid | wal
|
||||
persistent-path: ./data
|
||||
# EFS access-point emulation for the shared local Docker volumes that back ECS
|
||||
# efsVolumeConfiguration mounts. All opt-in; with no overrides a shared volume is a plain
|
||||
# named volume (root:root 0755), so existing behaviour is unchanged. See docs/services/ecs.md.
|
||||
efs:
|
||||
# owner-uid: 1001 # CreationInfo.OwnerUid (set together with owner-gid)
|
||||
# owner-gid: 1001 # CreationInfo.OwnerGid (set together with owner-uid)
|
||||
# root-permissions: "2775" # CreationInfo.Permissions; 3-4 octal digits (4-digit carries setgid/sticky)
|
||||
init-image: busybox:stable # image for the one-off chown/chmod of the volume root
|
||||
# mount-user: "1001:1001" # PosixUser: run mounting containers as uid[:gid]
|
||||
# mount-group-add: 2000 # supplementary gid added to mounting containers
|
||||
wal:
|
||||
compaction-interval-ms: 30000
|
||||
services:
|
||||
ssm:
|
||||
flush-interval-ms: 5000
|
||||
dynamodb:
|
||||
flush-interval-ms: 5000
|
||||
sns:
|
||||
flush-interval-ms: 5000
|
||||
lambda:
|
||||
flush-interval-ms: 5000
|
||||
cloudwatchlogs:
|
||||
flush-interval-ms: 15000
|
||||
cloudwatchmetrics:
|
||||
flush-interval-ms: 5000
|
||||
secretsmanager:
|
||||
flush-interval-ms: 5000
|
||||
acm:
|
||||
flush-interval-ms: 5000
|
||||
opensearch:
|
||||
flush-interval-ms: 5000
|
||||
fis:
|
||||
flush-interval-ms: 5000
|
||||
|
||||
dns:
|
||||
# Extra hostname suffixes resolved to Floci's container IP by the embedded DNS server.
|
||||
# The primary suffix (floci.hostname or derived from base-url) is always included.
|
||||
# Useful when migrating from LocalStack — Lambda functions that hardcode
|
||||
# localhost.localstack.cloud as their endpoint work without code changes.
|
||||
# Via env var (comma-separated): FLOCI_DNS_EXTRA_SUFFIXES=localhost.localstack.cloud,other.internal
|
||||
# extra-suffixes:
|
||||
# - localhost.localstack.cloud
|
||||
|
||||
auth:
|
||||
validate-signatures: false # Set to true to verify S3 presigned URL signatures
|
||||
presign-secret: local-emulator-secret # HMAC secret for S3 pre-signed URL verification
|
||||
|
||||
tls:
|
||||
enabled: false # FLOCI_TLS_ENABLED — enable HTTPS on all endpoints
|
||||
# cert-path: "" # FLOCI_TLS_CERT_PATH — PEM certificate file path
|
||||
# key-path: "" # FLOCI_TLS_KEY_PATH — PEM private key file path
|
||||
self-signed: true # FLOCI_TLS_SELF_SIGNED: auto-generate a cert signed by the local CA when no paths provided
|
||||
|
||||
protocols:
|
||||
max-request-size: 2048 # FLOCI_PROTOCOLS_MAX_REQUEST_SIZE — max HTTP request body size in MB (legacy: floci.max-request-size / FLOCI_MAX_REQUEST_SIZE)
|
||||
|
||||
docker:
|
||||
log-max-size: "10m" # Max size per container log file before rotation
|
||||
log-max-file: "3" # Number of rotated log files to retain
|
||||
docker-host: unix:///var/run/docker.sock # Docker daemon socket (shared by Lambda, RDS, ElastiCache)
|
||||
docker-config-path: "" # Path to dir containing Docker's config.json (e.g. /root/.docker)
|
||||
registry-credentials: [] # Per-registry explicit credentials for private registries
|
||||
|
||||
services:
|
||||
ssm:
|
||||
enabled: true
|
||||
max-parameter-history: 5 # Max versions kept per parameter
|
||||
|
||||
sqs:
|
||||
enabled: true
|
||||
default-visibility-timeout: 30 # Seconds
|
||||
max-message-size: 1048576 # Bytes (1 MiB, the AWS maximum)
|
||||
clear-fifo-deduplication-cache-on-purge: false # When true, PurgeQueue clears SQS FIFO dedup and SNS FIFO topic dedup for topics subscribed to that queue
|
||||
|
||||
s3:
|
||||
enabled: true
|
||||
default-presign-expiry-seconds: 3600
|
||||
|
||||
dynamodb:
|
||||
enabled: true
|
||||
|
||||
sns:
|
||||
enabled: true
|
||||
|
||||
lambda:
|
||||
enabled: true
|
||||
ephemeral: false # true = remove container after each invocation
|
||||
docker-flags: "" # Extra Docker create flags for Lambda containers
|
||||
ecr-base-uri: public.ecr.aws # Registry for Lambda runtime images (legacy: floci.ecr-base-uri / FLOCI_ECR_BASE_URI)
|
||||
honour-architectures: false # true = select the declared Lambda Docker architecture
|
||||
default-memory-mb: 128
|
||||
default-timeout-seconds: 3
|
||||
runtime-api-base-port: 12000 # Port range for Lambda Runtime API
|
||||
runtime-api-max-port: 12499 # One port per running container = concurrency ceiling
|
||||
code-path: ./data/lambda-code # Where ZIP archives are stored
|
||||
zip-max-entries: 100000 # Maximum ZIP entries extracted per deployment package
|
||||
poll-interval-ms: 1000
|
||||
container-idle-timeout-seconds: 300 # Remove idle containers after this
|
||||
region-concurrency-limit: 1000 # Concurrent executions ceiling per region
|
||||
unreserved-concurrency-min: 100 # Minimum unreserved capacity PutFunctionConcurrency must leave
|
||||
hot-reload:
|
||||
enabled: false # true = enable bind-mount hot-reload via S3Bucket=hot-reload
|
||||
# allowed-paths: # Optional allowlist of host paths that may be bind-mounted
|
||||
# - /home/user/projects
|
||||
# - /tmp
|
||||
|
||||
apigateway:
|
||||
enabled: true
|
||||
|
||||
apigatewayv2:
|
||||
enabled: true
|
||||
|
||||
iam:
|
||||
enabled: true
|
||||
enforcement-enabled: false # Set to true to enforce IAM policies on all requests
|
||||
seed-deployer-principal: false # Set to true to create a local floci-deployer admin principal
|
||||
|
||||
networkfirewall:
|
||||
enabled: true
|
||||
|
||||
servicequotas:
|
||||
enabled: true
|
||||
|
||||
ram:
|
||||
enabled: true
|
||||
|
||||
elasticache:
|
||||
enabled: true
|
||||
proxy-base-port: 6379
|
||||
proxy-max-port: 6399
|
||||
default-image: "valkey/valkey:8"
|
||||
|
||||
rds:
|
||||
enabled: true
|
||||
mock: false # true = clusters/instances created without Docker (useful for CI)
|
||||
proxy-base-port: 7001
|
||||
proxy-max-port: 7099
|
||||
# endpoint-host: localhost # Hostname clients use; enables published-port translation in Docker
|
||||
# Omit image overrides to adapt Floci's built-in images to EngineVersion.
|
||||
# Uncomment an override to pin that engine to an exact image.
|
||||
# default-postgres-image: "registry.example.com/postgres:16-alpine"
|
||||
# default-mysql-image: "registry.example.com/mysql:8.0"
|
||||
# default-mariadb-image: "registry.example.com/mariadb:11"
|
||||
default-sql-server-image: "mcr.microsoft.com/mssql/server:2022-latest"
|
||||
|
||||
rds-data:
|
||||
enabled: true
|
||||
transaction-ttl-seconds: 180
|
||||
|
||||
eventbridge:
|
||||
enabled: true
|
||||
|
||||
scheduler:
|
||||
enabled: true
|
||||
|
||||
cloudwatchlogs:
|
||||
enabled: true
|
||||
max-events-per-query: 10000
|
||||
max-stored-events: 20000 # per account; oldest events are evicted once the store exceeds this
|
||||
|
||||
cloudwatchmetrics:
|
||||
enabled: true
|
||||
|
||||
secretsmanager:
|
||||
enabled: true
|
||||
default-recovery-window-days: 30
|
||||
|
||||
kinesis:
|
||||
enabled: true
|
||||
|
||||
kms:
|
||||
enabled: true
|
||||
|
||||
cognito:
|
||||
enabled: true
|
||||
|
||||
stepfunctions:
|
||||
enabled: true
|
||||
|
||||
cloudformation:
|
||||
enabled: true
|
||||
|
||||
acm:
|
||||
enabled: true
|
||||
validation-wait-seconds: 0 # Seconds before transitioning PENDING_VALIDATION → ISSUED
|
||||
|
||||
ses:
|
||||
enabled: true
|
||||
# smtp-host: mailpit # SMTP server for email relay (empty = store only)
|
||||
# smtp-port: 1025
|
||||
# smtp-user: ""
|
||||
# smtp-pass: ""
|
||||
# smtp-starttls: DISABLED # DISABLED, OPTIONAL, or REQUIRED
|
||||
|
||||
opensearch:
|
||||
enabled: true
|
||||
mock: false # true = metadata only, no Docker (useful for CI)
|
||||
default-image: "opensearchproject/opensearch:2"
|
||||
proxy-base-port: 9400
|
||||
proxy-max-port: 9499
|
||||
keep-running-on-shutdown: false # leave containers running after Floci stops
|
||||
# docker network is inherited from floci.services.docker-network
|
||||
|
||||
ec2:
|
||||
enabled: true
|
||||
|
||||
efs:
|
||||
enabled: true
|
||||
|
||||
ecs:
|
||||
enabled: true
|
||||
mock: false # true = tasks go to RUNNING without Docker (useful for CI)
|
||||
|
||||
appsync:
|
||||
enabled: true
|
||||
|
||||
appconfig:
|
||||
enabled: true
|
||||
|
||||
appconfigdata:
|
||||
enabled: true
|
||||
|
||||
fis:
|
||||
enabled: true
|
||||
|
||||
ecr:
|
||||
enabled: true
|
||||
registry-image: "registry:2"
|
||||
registry-container-name: floci-ecr-registry
|
||||
registry-base-port: 5100 # private loopback backing port range
|
||||
registry-max-port: 5199
|
||||
data-path: ./data/ecr
|
||||
tls-enabled: false
|
||||
keep-running-on-shutdown: true
|
||||
uri-style: hostname # hostname | path
|
||||
prefer-local-images: true
|
||||
```
|
||||
|
||||
### Initialization hooks
|
||||
|
||||
`floci.init-hooks.*` is accepted as an override but is not declared in the shipped `application.yml`. See [Initialization Hooks](../initialization-hooks.md) for the full list of keys (`shell-executable`, `timeout-seconds`, `shutdown-grace-period-seconds`) and their defaults.
|
||||
|
||||
## Service Limits
|
||||
|
||||
All keys in this table are declared on `EmulatorConfig` and accept environment variable overrides via the `FLOCI_` prefix.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------------------------------------------------|------------------|---------------------------------------------------------------|
|
||||
| `FLOCI_PROTOCOLS_MAX_REQUEST_SIZE` | `2048` | Max HTTP request body size in MB (legacy: `FLOCI_MAX_REQUEST_SIZE`) |
|
||||
| `FLOCI_DEFAULT_REGION` | `us-east-1` | Default AWS region used in ARNs and response URLs |
|
||||
| `FLOCI_DEFAULT_AVAILABILITY_ZONE` | `us-east-1a` | Default AZ reported by EC2, RDS, and other AZ-aware services |
|
||||
| `FLOCI_DEFAULT_ACCOUNT_ID` | `000000000000` | Default AWS account ID used in ARNs |
|
||||
| `FLOCI_SERVICES_LAMBDA_ECR_BASE_URI` | `public.ecr.aws` | Registry used when pulling Lambda runtime images (legacy: `FLOCI_ECR_BASE_URI`) |
|
||||
| `FLOCI_DNS_EXTRA_SUFFIXES` | *(unset)* | Comma-separated extra hostname suffixes the embedded DNS server resolves to Floci's container IP. E.g. `localhost.localstack.cloud,localhost.example.internal` |
|
||||
| `FLOCI_SERVICES_SSM_MAX_PARAMETER_HISTORY` | `5` | Max parameter versions kept |
|
||||
| `FLOCI_SERVICES_SQS_DEFAULT_VISIBILITY_TIMEOUT` | `30` | Default visibility timeout (seconds) |
|
||||
| `FLOCI_SERVICES_SQS_MAX_MESSAGE_SIZE` | `1048576` | Max message size (bytes) |
|
||||
| `FLOCI_SERVICES_SQS_CLEAR_FIFO_DEDUPLICATION_CACHE_ON_PURGE` | `false` | When `true`, `PurgeQueue` clears the FIFO 5-minute deduplication cache for the target queue and matching SNS FIFO topic dedup entries |
|
||||
| `FLOCI_SERVICES_S3_DEFAULT_PRESIGN_EXPIRY_SECONDS` | `3600` | Pre-signed URL expiry |
|
||||
| `FLOCI_SERVICES_DOCKER_NETWORK` | *(unset)* | Shared Docker network for Lambda, RDS, ElastiCache containers |
|
||||
| `FLOCI_SERVICES_RDS_DATA_ENABLED` | `true` | Enable the RDS Data API service |
|
||||
| `FLOCI_SERVICES_RDS_DATA_TRANSACTION_TTL_SECONDS` | `180` | Idle timeout, in seconds, before leaked RDS Data API transactions expire |
|
||||
| `FLOCI_SERVICES_ECS_MOCK` | `false` | Skip Docker; tasks go straight to RUNNING (useful for CI) |
|
||||
| `FLOCI_SERVICES_ECS_DOCKER_NETWORK` | *(unset)* | Docker network for ECS task containers |
|
||||
| `FLOCI_SERVICES_ECS_DEFAULT_MEMORY_MB` | `512` | Default memory (MB) when task definition omits it |
|
||||
| `FLOCI_SERVICES_ECS_DEFAULT_CPU_UNITS` | `256` | Default CPU units when task definition omits it |
|
||||
| `FLOCI_SERVICES_ECS_HOST_VOLUME_ROOTS` | *(unset)* | Comma-separated allowlist of parent directories for host volume bind mounts; by default (unset, and `ALLOW_UNSAFE_HOST_VOLUMES=false`) every host volume `sourcePath` is rejected |
|
||||
| `FLOCI_SERVICES_ECS_ALLOW_UNSAFE_HOST_VOLUMES` | `false` | Allow any host path, bypassing the host-volume-roots allowlist (traversal, bare root, and the Docker socket or an ancestor directory of it are still always rejected) |
|
||||
| `FLOCI_SERVICES_IAM_ENFORCEMENT_ENABLED` | `false` | Enforce IAM identity-based policies on every request when `true` |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_MOCK` | `false` | Skip Docker; domains appear active immediately (useful for CI) |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_KEEP_RUNNING_ON_SHUTDOWN` | `false` | Leave OpenSearch containers running after Floci stops |
|
||||
| `FLOCI_SERVICES_SES_SMTP_HOST` | *(unset)* | SMTP server host for SES email relay (empty = store only) |
|
||||
| `FLOCI_SERVICES_SES_SMTP_PORT` | `25` | SMTP server port |
|
||||
| `FLOCI_SERVICES_SES_SMTP_USER` | *(unset)* | SMTP authentication username |
|
||||
| `FLOCI_SERVICES_SES_SMTP_PASS` | *(unset)* | SMTP authentication password |
|
||||
| `FLOCI_SERVICES_SES_SMTP_STARTTLS` | `DISABLED` | STARTTLS mode: `DISABLED`, `OPTIONAL`, or `REQUIRED` |
|
||||
| `FLOCI_SERVICES_LAMBDA_HONOUR_ARCHITECTURES` | `false` | Select the declared Lambda architecture for Docker image pulls and containers |
|
||||
| `FLOCI_SERVICES_LAMBDA_ZIP_MAX_ENTRIES` | `100000` | Maximum number of entries accepted in a Lambda ZIP archive |
|
||||
| `FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ENABLED` | `false` | Enable bind-mount hot-reload mode (`S3Bucket=hot-reload`) |
|
||||
| `FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ALLOWED_PATHS` | *(unset)* | Comma-separated list of host paths allowed as bind-mount roots; unset = any absolute path |
|
||||
|
||||
Enable `FLOCI_SERVICES_LAMBDA_HONOUR_ARCHITECTURES` only when the Docker host can run the
|
||||
function architecture. Foreign architectures require Docker Desktop support or host emulation
|
||||
such as `binfmt_misc` with QEMU. Floci does not fall back to the host architecture when this
|
||||
setting is enabled.
|
||||
|
||||
Per-queue SQS redrive policy (`maxReceiveCount`) is configured at queue creation time via `SetQueueAttributes` / `CreateQueue`, not as a global default.
|
||||
|
||||
`FLOCI_DEFAULT_AVAILABILITY_ZONE` is declared in `EmulatorConfig` but not in the shipped `application.yml`, so it falls through to the `@WithDefault` value above when unset.
|
||||
|
||||
The flat keys `floci.max-request-size` / `FLOCI_MAX_REQUEST_SIZE` and `floci.ecr-base-uri` / `FLOCI_ECR_BASE_URI` moved into sub-structures in 2.x (`floci.protocols.max-request-size` and `floci.services.lambda.ecr-base-uri`). The old names still resolve — a `FlociConfigRelocationsInterceptor` maps them onto the new keys — so existing configs and env vars keep working.
|
||||
|
||||
## Disabling Services
|
||||
|
||||
Set `enabled: false` for any service you don't need. Disabled services return a `ServiceUnavailableException` rather than silently ignoring calls.
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
services:
|
||||
cloudformation:
|
||||
enabled: false
|
||||
stepfunctions:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Via environment variable — set to `false` for any `FLOCI_SERVICES_<SERVICE>_ENABLED` key. See [Environment Variables Reference](../environment-variables.md#services-core) for the full list.
|
||||
|
||||
## Logging
|
||||
|
||||
Floci uses standard [Quarkus logging](https://quarkus.io/guides/logging). The default effective level is `INFO`. Each service logs operation-level events at `DEBUG` (IDs and target resources) and full request/response payloads at `TRACE` — useful when diagnosing TestContainers-based test failures.
|
||||
|
||||
Floci ships with `quarkus.log.min-level: TRACE`, so raising a single category to `TRACE` is enough; you don't need to change the min-level yourself.
|
||||
|
||||
**Enable TRACE for a service via environment variables:**
|
||||
|
||||
```bash
|
||||
# SQS: log SendMessage/ReceiveMessage/DeleteMessage bodies and attributes
|
||||
QUARKUS_LOG_CATEGORY__IO_GITHUB_HECTORVENT_FLOCI_SERVICES_SQS__LEVEL=TRACE
|
||||
|
||||
# DynamoDB: log PutItem/GetItem/UpdateItem/DeleteItem items, Query/Scan counts
|
||||
QUARKUS_LOG_CATEGORY__IO_GITHUB_HECTORVENT_FLOCI_SERVICES_DYNAMODB__LEVEL=TRACE
|
||||
```
|
||||
|
||||
**Or in `application.yml`:**
|
||||
|
||||
```yaml
|
||||
quarkus:
|
||||
log:
|
||||
category:
|
||||
"io.github.hectorvent.floci.services.sqs":
|
||||
level: TRACE
|
||||
"io.github.hectorvent.floci.services.dynamodb":
|
||||
level: TRACE
|
||||
```
|
||||
|
||||
**TestContainers example:**
|
||||
|
||||
```java
|
||||
new GenericContainer<>("floci/floci:latest")
|
||||
.withExposedPorts(4566)
|
||||
.withEnv("QUARKUS_LOG_CATEGORY__IO_GITHUB_HECTORVENT_FLOCI_SERVICES_SQS__LEVEL", "TRACE");
|
||||
```
|
||||
|
||||
TRACE output includes the payload alongside the existing DEBUG line:
|
||||
|
||||
```
|
||||
DEBUG [SqsService] Sent message aa7b93e7-... to queue .../events
|
||||
TRACE [SqsService] Sent message aa7b93e7-... to queue .../events body={"eventType":"..."} attributes={source=okta}
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
# application.yml Reference
|
||||
|
||||
!!! note "Moved"
|
||||
This page has moved to [Advanced → application.yml Reference](./advanced/application-yml.md).
|
||||
|
||||
If you run the published Docker image, you don't need an `application.yml` — all settings are configured through `FLOCI_*` environment variables. See the [Environment Variables Reference](./environment-variables.md).
|
||||
@@ -0,0 +1,183 @@
|
||||
# Running with Docker
|
||||
|
||||
Floci is distributed as a Docker image. All configuration is done through environment variables — no config files or volume-mounted YAML is required.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
docker run --rm -p 4566:4566 floci/floci:latest
|
||||
```
|
||||
|
||||
That's it. The default configuration works out of the box for most services: SQS, SNS, S3, DynamoDB, SSM, Lambda, API Gateway, Cognito, KMS, Kinesis, Secrets Manager, CloudFormation, Step Functions, IAM, STS, EventBridge, Scheduler, and CloudWatch.
|
||||
|
||||
## Docker Compose
|
||||
|
||||
### Minimal (stateless)
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
ports:
|
||||
- "4566:4566"
|
||||
environment:
|
||||
FLOCI_HOSTNAME: floci
|
||||
```
|
||||
|
||||
### With persistence
|
||||
|
||||
Add two env vars and a volume — no config file needed:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
ports:
|
||||
- "4566:4566"
|
||||
volumes:
|
||||
- floci-data:/app/data
|
||||
environment:
|
||||
FLOCI_HOSTNAME: floci
|
||||
FLOCI_STORAGE_MODE: persistent
|
||||
FLOCI_STORAGE_PERSISTENT_PATH: /app/data
|
||||
|
||||
volumes:
|
||||
floci-data:
|
||||
```
|
||||
|
||||
### With ElastiCache and RDS
|
||||
|
||||
ElastiCache and RDS proxy TCP connections to real Docker containers. Those containers' ports must be reachable from your host, so additional port ranges are exposed. The Docker socket is required for Floci to manage those containers:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
ports:
|
||||
- "4566:4566"
|
||||
- "6379-6399:6379-6399" # ElastiCache proxy ports
|
||||
- "7001-7099:7001-7099" # RDS proxy ports
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- floci-data:/app/data
|
||||
environment:
|
||||
FLOCI_HOSTNAME: floci
|
||||
FLOCI_SERVICES_DOCKER_NETWORK: myproject_default
|
||||
FLOCI_STORAGE_MODE: persistent
|
||||
FLOCI_STORAGE_PERSISTENT_PATH: /app/data
|
||||
|
||||
volumes:
|
||||
floci-data:
|
||||
```
|
||||
|
||||
!!! warning "Docker socket"
|
||||
Lambda, ElastiCache, RDS, OpenSearch, and MSK require access to the Docker socket (`/var/run/docker.sock`) to spawn and manage containers. If you don't use these services, you can omit that volume.
|
||||
|
||||
!!! note "ECR data plane"
|
||||
ECR uses Floci's `4566` listener for Docker push and pull. The `registry:2` sidecar has a loopback-only implementation port; no ECR port range belongs in the Floci service's `ports` list. See [Ports Reference: ECR](./ports.md#ports-51005199--ecr-registry-backing-ports).
|
||||
|
||||
## Multi-container Networking
|
||||
|
||||
By default Floci embeds `localhost` in response URLs — for example, SQS queue URLs look like `http://localhost:4566/000000000000/my-queue`. This works when your application runs on the same machine, but breaks inside Docker Compose because other containers cannot reach `localhost` of the Floci container.
|
||||
|
||||
Set `FLOCI_HOSTNAME` to the Compose service name so Floci uses that name in every URL it generates:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
ports:
|
||||
- "4566:4566"
|
||||
environment:
|
||||
FLOCI_HOSTNAME: floci # (1)
|
||||
|
||||
app:
|
||||
build: .
|
||||
environment:
|
||||
AWS_ENDPOINT_URL: http://floci:4566
|
||||
depends_on:
|
||||
- floci
|
||||
```
|
||||
|
||||
1. Must match the Compose service name so other containers can resolve it by DNS.
|
||||
|
||||
With this setting Floci returns URLs like `http://floci:4566/000000000000/my-queue` that other containers can reach.
|
||||
|
||||
Floci automatically attaches Lambda containers it spawns to the same Compose network when no explicit Docker network is configured. Setting `FLOCI_HOSTNAME` ensures those containers receive a reachable endpoint, and that response fields such as SQS `QueueUrl` use the Docker service name instead of `localhost`.
|
||||
|
||||
Fields affected:
|
||||
|
||||
- SQS — `QueueUrl`
|
||||
- SNS — topic ARN callback URLs and subscription endpoints
|
||||
- Any pre-signed URL or callback generated from `FLOCI_BASE_URL`
|
||||
|
||||
!!! tip "CI pipelines"
|
||||
In GitHub Actions or GitLab CI where both your app and Floci run as `services`, set `FLOCI_HOSTNAME` to the service name (e.g. `floci`) and point your SDK at `http://floci:4566`.
|
||||
|
||||
## Initialization Hooks
|
||||
|
||||
Mount shell scripts into hook directories to run setup or teardown logic at each lifecycle phase. No configuration variable is needed — Floci detects scripts by directory:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest-compat
|
||||
ports:
|
||||
- "4566:4566"
|
||||
volumes:
|
||||
- ./init/boot.d:/etc/floci/init/boot.d:ro # before storage loads — no AWS APIs yet
|
||||
- ./init/start.d:/etc/floci/init/start.d:ro # after HTTP server is ready
|
||||
- ./init/ready.d:/etc/floci/init/ready.d:ro # after all start hooks complete
|
||||
- ./init/stop.d:/etc/floci/init/stop.d:ro # during shutdown, while HTTP is still up
|
||||
```
|
||||
|
||||
Use the `latest-compat` image when your scripts call `aws` or `boto3` — it includes the AWS CLI and boto3 pre-configured for the local endpoint, so no `--endpoint-url` flag is needed.
|
||||
|
||||
If you have existing LocalStack init scripts, mount them under the LocalStack-compat paths and they run unchanged:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./localstack-init/ready.d:/etc/localstack/init/ready.d:ro
|
||||
```
|
||||
|
||||
See [Initialization Hooks](./initialization-hooks.md) for execution order, script types, and exit-code behavior.
|
||||
|
||||
## CI Pipeline Example
|
||||
|
||||
```yaml title=".github/workflows/test.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
ports:
|
||||
- "4566:4566"
|
||||
|
||||
steps:
|
||||
- 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: mvn test
|
||||
```
|
||||
|
||||
## Common Environment Variables
|
||||
|
||||
The most frequently set variables when running Floci as a Docker image:
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `FLOCI_HOSTNAME` | _(none)_ | Hostname embedded in response URLs. Set to the Compose service name in multi-container setups |
|
||||
| `FLOCI_DEFAULT_REGION` | `us-east-1` | AWS region reported in ARNs and responses |
|
||||
| `FLOCI_DEFAULT_ACCOUNT_ID` | `000000000000` | AWS account ID used in ARNs |
|
||||
| `FLOCI_STORAGE_MODE` | `memory` | `memory`, `persistent`, `hybrid`, or `wal` |
|
||||
| `FLOCI_STORAGE_PERSISTENT_PATH` | `./data` | Directory for persistent storage |
|
||||
| `FLOCI_SERVICES_DOCKER_NETWORK` | _(none)_ | Docker network for spawned containers (Lambda, ElastiCache, RDS, OpenSearch, MSK) |
|
||||
| `FLOCI_AUTH_VALIDATE_SIGNATURES` | `false` | Verify S3 presigned URL signatures |
|
||||
| `FLOCI_SERVICES_LAMBDA_EPHEMERAL` | `false` | Remove Lambda containers after each invocation |
|
||||
|
||||
For the complete list of every `FLOCI_*` variable, see [Environment Variables Reference](./environment-variables.md).
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
For Docker daemon socket, private registry authentication, log rotation, and network settings see [Docker Configuration](./docker.md).
|
||||
@@ -0,0 +1,176 @@
|
||||
# Docker Images
|
||||
|
||||
Floci publishes images to [Docker Hub (`floci/floci`)](https://hub.docker.com/r/floci/floci).
|
||||
|
||||
Every image tag combines two independent choices: **what's inside** (variant) and **how stable it is** (channel).
|
||||
|
||||
## Axis 1: Variant (what's inside)
|
||||
|
||||
| Variant | Contents | When to use |
|
||||
|---|---|---|
|
||||
| **Standard** | Floci native binary only | General use: CI, local dev, Testcontainers **(recommended)** |
|
||||
| **Baseline** | Floci native binary compiled for ARMv8.0 (`armv8-a`) | ARM64 hosts without LSE, including Raspberry Pi 4-class CPUs |
|
||||
| **Compat** | Floci + Python 3 + AWS CLI + boto3 | Workflows that need AWS tooling available inside the container |
|
||||
|
||||
The compat image runs the same native binary as the standard image, so startup time and memory footprint are identical. Only the image size increases. The baseline image is different: it is an ARM64-only release artifact compiled for the ARMv8.0 ISA floor and is not published on nightly channels.
|
||||
|
||||
The standard image is built on Red Hat UBI 9 micro. Besides Floci it contains only bash and coreutils. There is no package manager, curl, grep or sed inside. Pick the compat image when you need tools inside the container.
|
||||
|
||||
## Axis 2: Channel (how stable)
|
||||
|
||||
| Channel | Source | Published |
|
||||
|---|---|---|
|
||||
| **Release** | Tagged version (e.g. `x.y.z`) | 1st and 3rd Tuesday of each month |
|
||||
| **Nightly** | Tip of `main` | Every night at 23:00 CT |
|
||||
|
||||
Release images are stable and recommended for most use cases. Between trains, `nightly` carries every merged fix from the following morning. Nightly images track active development and may include unreleased changes.
|
||||
|
||||
## Full Tag Matrix
|
||||
|
||||
Combining both axes gives the complete set of published tags:
|
||||
|
||||
| | Standard | Baseline (ARM64 only) | Compat |
|
||||
|---|---|---|---|
|
||||
| **Release (latest)** | `latest` ✅ | `latest-baseline` | `latest-compat` |
|
||||
| **Release (pinned)** | `x.y.z` | `x.y.z-baseline` | `x.y.z-compat` |
|
||||
| **Nightly (floating)** | `nightly` | — | `nightly-compat` |
|
||||
| **Nightly (dated)** | `nightly-mmddyyyy` | — | `nightly-mmddyyyy-compat` |
|
||||
|
||||
Dated nightly tags (e.g. `nightly-05022026`) name one night's build of `main`. A same-day rerun of the nightly workflow republishes that day's tag, so for a build you can rely on not changing, pin a release version.
|
||||
|
||||
!!! warning
|
||||
Nightly images may include unreleased or experimental changes. Use release tags in production-like environments.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
# Standard release : recommended
|
||||
image: floci/floci:latest
|
||||
|
||||
# Compat release : includes AWS CLI and boto3
|
||||
image: floci/floci:latest-compat
|
||||
|
||||
# ARM64 baseline release : Raspberry Pi 4 / pre-LSE AArch64 cores
|
||||
image: floci/floci:latest-baseline
|
||||
|
||||
# Pinned release : reproducible builds
|
||||
image: floci/floci:x.y.z
|
||||
|
||||
# Nightly : track main
|
||||
image: floci/floci:nightly
|
||||
```
|
||||
|
||||
## Multi-Architecture
|
||||
|
||||
Standard and compat images are published as multi-arch manifests supporting `linux/amd64` and `linux/arm64`. Baseline images are intentionally `linux/arm64` only.
|
||||
|
||||
## Raspberry Pi 4 and older ARM64 CPUs
|
||||
|
||||
If the standard ARM64 image exits with `CPU features [FP, ASIMD, CRC32, LSE] not supported`, use the `-baseline` release tag. The baseline image is compiled with GraalVM `-march=armv8-a`, so it does not require LSE. For example, use `floci/floci:latest-baseline` or pin `floci/floci:x.y.z-baseline`.
|
||||
|
||||
The baseline variant currently covers the Docker image only. The standalone `floci-linux-arm64` binary remains tracked in [#1114](https://github.com/floci-io/floci/issues/1114).
|
||||
|
||||
## Reusable Image Publishing
|
||||
|
||||
The `Build Floci Images` reusable workflow publishes a JVM image for an exact
|
||||
branch, tag, or commit by default. This matches the ordinary Maven package and
|
||||
the repository's local-development Docker image. A calling repository owns the
|
||||
trigger, destination registry, credentials, and package permissions; the shared
|
||||
workflow owns the multi-architecture manifest, image labels, and provenance
|
||||
output.
|
||||
|
||||
For example, a fork can keep this small manual caller on its default branch:
|
||||
|
||||
```yaml title=".github/workflows/publish-images.yml"
|
||||
name: Publish Floci Images
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source-ref:
|
||||
description: Floci branch, tag, or commit to publish
|
||||
required: true
|
||||
type: string
|
||||
default: main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
uses: floci-io/floci/.github/workflows/build-images.yml@main
|
||||
with:
|
||||
source-ref: ${{ inputs.source-ref }}
|
||||
image: ghcr.io/${{ github.repository_owner }}/floci
|
||||
```
|
||||
|
||||
Pin the reusable workflow to a trusted tag or full commit SHA when the caller
|
||||
requires a stable build contract. The caller's `GITHUB_TOKEN` publishes a GHCR
|
||||
image only below the caller's repository owner. Calls targeting another OCI
|
||||
registry must pass `REGISTRY_USERNAME` and `REGISTRY_TOKEN` through the reusable
|
||||
workflow's declared secrets.
|
||||
|
||||
The default workflow publishes a build-and-commit-derived convenience tag:
|
||||
|
||||
- `<YYYYMMDD.HHMMSS>.<commit-sha-8>` for the JVM image
|
||||
|
||||
Callers that also need native artifacts can pass `publish-native: true`. This
|
||||
adds `<YYYYMMDD.HHMMSS>.<commit-sha-8>-native` and
|
||||
`<YYYYMMDD.HHMMSS>.<commit-sha-8>-native-compat`; it does not change the
|
||||
unqualified JVM tag. Timestamps are generated in UTC.
|
||||
|
||||
Each workflow invocation produces a new timestamped tag. The workflow returns
|
||||
the digest-qualified JVM reference (plus native references when requested) and
|
||||
uploads them in a provenance JSON artifact. Downstream tests should use a
|
||||
digest-qualified reference when they require an immutable image:
|
||||
|
||||
```text
|
||||
ghcr.io/example/floci@sha256:...
|
||||
```
|
||||
|
||||
## What's in the Compat Image
|
||||
|
||||
The compat image adds the following to the standard image contents:
|
||||
|
||||
- Python 3 + pip
|
||||
- [AWS CLI](https://pypi.org/project/awscli/) (via pip)
|
||||
- [boto3](https://pypi.org/project/boto3/) (via pip)
|
||||
|
||||
The AWS CLI is pre-configured to talk to the local Floci endpoint, so no `--endpoint-url` flag is needed in hook scripts:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
aws sqs create-queue --queue-name my-queue # works without --endpoint-url
|
||||
aws s3 mb s3://my-bucket
|
||||
```
|
||||
|
||||
The following environment variables are set in both the standard and compat images:
|
||||
|
||||
| Variable | Value |
|
||||
|---|---|
|
||||
| `AWS_DEFAULT_REGION` | `us-east-1` |
|
||||
| `AWS_ACCESS_KEY_ID` | `test` |
|
||||
| `AWS_SECRET_ACCESS_KEY` | `test` |
|
||||
| `AWS_CONFIG_FILE` | `/etc/floci/aws/config` |
|
||||
|
||||
The compat image additionally sets:
|
||||
|
||||
| Variable | Value |
|
||||
|---|---|
|
||||
| `AWS_ENDPOINT_URL` | `http://localhost:4566` |
|
||||
|
||||
Override any of them at runtime via `docker run -e` or the Compose `environment` block.
|
||||
|
||||
## Local Development
|
||||
|
||||
The project ships a `docker-compose.yml` at the repository root configured for local development.
|
||||
By default it uses `docker/Dockerfile`, a fast Ubuntu Noble/glibc JVM image suited for iteration
|
||||
with Java 25. The release and nightly images use the native Dockerfiles described above. Switch the
|
||||
`dockerfile` entry to test the native image locally:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.native # or docker/Dockerfile for fast JVM dev build
|
||||
```
|
||||
@@ -0,0 +1,266 @@
|
||||
# Docker Configuration
|
||||
|
||||
Floci spawns real Docker containers for services that need them: Lambda, RDS, ElastiCache, OpenSearch, MSK, and ECS. All of these share the same Docker client configuration, controlled under `floci.docker`.
|
||||
|
||||
## Docker Daemon Socket
|
||||
|
||||
By default Floci connects to the local Docker daemon via the Unix socket. Override it with `docker-host` when needed (e.g. a remote Docker host or a non-standard socket path):
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
docker:
|
||||
docker-host: unix:///var/run/docker.sock
|
||||
```
|
||||
|
||||
**Running natively on Windows** (not inside WSL or a container): Windows has no equivalent of `/var/run/docker.sock`, so when `docker-host` is left at its default and `DOCKER_HOST` isn't set, Floci automatically falls back to Docker Desktop's named pipe (`npipe:////./pipe/docker_engine`) instead. An explicit `docker-host` or `DOCKER_HOST` always takes priority over this fallback.
|
||||
|
||||
Environment variable: `FLOCI_DOCKER_DOCKER_HOST`
|
||||
|
||||
When running Floci inside Docker Compose, mount the host socket:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
The container normally switches to the unprivileged `floci` user (UID 1001), including when started with `--user root`. If the mounted socket is accessible only to root, set the entrypoint option `FLOCI_RUN_AS_ROOT=true` to keep Floci running as root:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 4566:4566 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e FLOCI_RUN_AS_ROOT=true \
|
||||
floci/floci:latest
|
||||
```
|
||||
|
||||
In Docker Compose, use the same environment variable:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
FLOCI_RUN_AS_ROOT: "true"
|
||||
```
|
||||
|
||||
Use this only when the socket's permissions require root. Without the option, Floci retains its unprivileged default. The setting applies to the container entrypoint, not to `floci.docker` configuration.
|
||||
|
||||
## Private Registry Authentication
|
||||
|
||||
Any service that pulls a container image from a private registry (Lambda image functions, custom OpenSearch images, private Postgres images, etc.) needs Docker credentials. Two approaches are supported and can be combined.
|
||||
|
||||
### Mount the host Docker config
|
||||
|
||||
Reuses existing `docker login` sessions and credential helpers from the host machine. Mount the host `~/.docker` directory and point Floci at it:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ~/.docker:/root/.docker:ro
|
||||
environment:
|
||||
FLOCI_DOCKER_DOCKER_CONFIG_PATH: /root/.docker
|
||||
```
|
||||
|
||||
Or in `application.yml`:
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
docker:
|
||||
docker-config-path: /root/.docker
|
||||
```
|
||||
|
||||
This works with any credential helper configured on the host (`docker-credential-desktop`, `ecr-credential-helper`, etc.) as long as the helper binary is also available inside the Floci container.
|
||||
|
||||
### Explicit per-registry credentials
|
||||
|
||||
For CI environments or air-gapped setups where mounting the host filesystem is not practical:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
environment:
|
||||
FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__SERVER: myregistry.example.com
|
||||
FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__USERNAME: myuser
|
||||
FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__PASSWORD: mypassword
|
||||
# Add more registries by incrementing the index:
|
||||
# FLOCI_DOCKER_REGISTRY_CREDENTIALS_1__SERVER: other.registry.io
|
||||
# FLOCI_DOCKER_REGISTRY_CREDENTIALS_1__USERNAME: ...
|
||||
# FLOCI_DOCKER_REGISTRY_CREDENTIALS_1__PASSWORD: ...
|
||||
```
|
||||
|
||||
Or in `application.yml`:
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
docker:
|
||||
registry-credentials:
|
||||
- server: myregistry.example.com
|
||||
username: myuser
|
||||
password: mypassword
|
||||
- server: other.registry.io
|
||||
username: otheruser
|
||||
password: otherpassword
|
||||
```
|
||||
|
||||
The `server` field must match the registry hostname exactly as it appears in the image URI (e.g. `myregistry.example.com` for `myregistry.example.com/repo:tag`). Docker Hub images (e.g. `ubuntu:22.04`) have an empty hostname and are not matched by any explicit credential entry — use the Docker config mount approach for Docker Hub authentication.
|
||||
|
||||
### Precedence
|
||||
|
||||
Explicit credentials take precedence for registries they cover. For everything else, Floci falls back to the Docker config file (if `docker-config-path` is set) and then to an anonymous pull.
|
||||
|
||||
## Container Log Settings
|
||||
|
||||
Configure log rotation for all containers spawned by Floci:
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
docker:
|
||||
log-max-size: "10m" # Max size per log file before rotation (Docker json-file format)
|
||||
log-max-file: "3" # Number of rotated log files to retain per container
|
||||
```
|
||||
|
||||
## Container Labels
|
||||
|
||||
Every container and volume Floci creates carries three reserved labels for discovery and cleanup:
|
||||
|
||||
| Label | Value | Purpose |
|
||||
|---|---|---|
|
||||
| `floci` | `true` | Umbrella across all Floci emulators — `docker ps --filter label=floci=true` |
|
||||
| `floci_emulator` | `floci-aws` | Per-emulator discriminator |
|
||||
| `floci_namespace` | *(the configured namespace)* | Only present when `resource-namespace` is set |
|
||||
|
||||
Add your own labels with `extra-labels` — they are applied to every container **and** volume Floci creates (Lambda functions and code volumes, RDS databases, ElastiCache, OpenSearch, MSK, ECS, ...):
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
docker:
|
||||
extra-labels:
|
||||
- key: "com.example.project"
|
||||
value: my-project
|
||||
- key: environment
|
||||
value: dev
|
||||
```
|
||||
|
||||
Or via environment variables (indexed entries, like `registry-credentials`):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
environment:
|
||||
FLOCI_DOCKER_EXTRA_LABELS_0__KEY: "com.example.project"
|
||||
FLOCI_DOCKER_EXTRA_LABELS_0__VALUE: my-project
|
||||
FLOCI_DOCKER_EXTRA_LABELS_1__KEY: environment
|
||||
FLOCI_DOCKER_EXTRA_LABELS_1__VALUE: dev
|
||||
```
|
||||
|
||||
Extra labels are a list of key/value entries rather than a map so that label keys containing dots, colons, or uppercase characters survive the environment-variable naming convention.
|
||||
|
||||
!!! note
|
||||
Entries using one of the reserved keys (`floci`, `floci_emulator`, `floci_namespace`) are ignored with a warning — user configuration can never break Floci's own container discovery and volume pruning.
|
||||
|
||||
### Resource Identity Labels
|
||||
|
||||
A container backing an emulated AWS resource also carries labels tying it back to that resource, additive to the labels above:
|
||||
|
||||
| Label | Value | Purpose |
|
||||
|---|---|---|
|
||||
| `io.floci` | `aws` | Cloud provider, for multi-cloud discovery when several Floci-like emulators share a host |
|
||||
| `io.floci.service` | e.g. `rds` | The AWS service the container backs |
|
||||
| `io.floci.resource-id` | e.g. `orders-db-primary` | The resource's identifier (DB instance identifier, cluster name, function name, ...) |
|
||||
| `io.floci.account` | the resolved account id | The AWS account the resource belongs to |
|
||||
| `io.floci.region` | the resolved region | The AWS region the resource belongs to |
|
||||
|
||||
This makes `docker ps --filter label=io.floci.resource-id=orders-db-primary` resolve a specific emulated resource to its backing container directly, without inferring it from names or creation order. Applied to RDS, DocDB, ElastiCache (Redis/Valkey and Memcached), MemoryDB, Neptune, MSK, OpenSearch, ECS, EKS, AmazonMQ, MWAA, Kinesis Data Analytics (Flink), Batch, CodeBuild, Lambda, and EC2. ECR's backing registry container carries every label except `io.floci.resource-id`, since it is a shared singleton with no single resource identifier.
|
||||
|
||||
## Docker Network
|
||||
|
||||
Containers spawned by Floci (Lambda, RDS, ElastiCache, OpenSearch, MSK, ECS) need to be on the same Docker network to communicate with each other and with Floci itself.
|
||||
|
||||
When Floci itself runs inside Docker and no network is configured, it automatically detects the current container's Docker network and uses it for spawned containers. You only need to set this manually when you want to force a specific network.
|
||||
|
||||
Set the shared network at the top level:
|
||||
|
||||
```yaml
|
||||
floci:
|
||||
services:
|
||||
docker-network: my-project_default
|
||||
```
|
||||
|
||||
Environment variable: `FLOCI_SERVICES_DOCKER_NETWORK`
|
||||
|
||||
Individual services can override the network with their own `docker-network` setting (e.g. `floci.services.lambda.docker-network`).
|
||||
|
||||
!!! tip
|
||||
In Docker Compose, the default network name is `<project-name>_default`. If your compose file is in a directory named `myapp`, the network is `myapp_default`.
|
||||
|
||||
## Running on Podman (rootless)
|
||||
|
||||
Floci runs under rootless Podman, but Podman's network topology needs a few
|
||||
explicit settings that Docker handles automatically. The following configuration
|
||||
is known to work:
|
||||
|
||||
```bash
|
||||
podman network create floci-net
|
||||
|
||||
podman run -d --name floci \
|
||||
--network floci-net \
|
||||
-p 4566:4566 \
|
||||
-v /run/user/$(id -u)/podman/podman.sock:/var/run/docker.sock:z \
|
||||
-e FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK=floci-net \
|
||||
-e FLOCI_HOSTNAME=floci \
|
||||
floci/floci
|
||||
```
|
||||
|
||||
What each setting does and why it is needed:
|
||||
|
||||
- **Named network (`floci-net`)** — the rootless default bridge does not assign
|
||||
reachable IPs between containers, so spawned Lambda containers cannot reach
|
||||
Floci. Create a named network and put both Floci and its Lambda containers on it.
|
||||
- **`FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK=floci-net`** — makes Floci attach the
|
||||
Lambda containers it spawns to that same named network.
|
||||
- **`FLOCI_HOSTNAME=floci`** — gives Floci a stable name that Lambda containers
|
||||
resolve when calling back to the Runtime API.
|
||||
- **`:z` on the socket mount** — relabels the Podman socket for SELinux. Without
|
||||
it, Floci fails to talk to the Podman socket: Lambda/ECR container creation
|
||||
errors with `java.io.IOException: Broken pipe`, and the **Floci UI** sidecar
|
||||
fails to launch with `java.net.BindException: Permission denied`. Use the
|
||||
lowercase `:z` (shared relabel) rather than `:Z` — the Podman API socket is
|
||||
shared with the Podman service, and `:Z` applies a container-private SELinux
|
||||
label that can break access. If `:z` is still not enough on your host, fall
|
||||
back to `--security-opt label=disable`.
|
||||
|
||||
!!! tip "When the Runtime API address is still unreachable"
|
||||
On some Podman network topologies the auto-detected Runtime API address
|
||||
(the host/IP Lambda containers use to call back into Floci) is still wrong,
|
||||
and invocations fail with `connect ECONNREFUSED <ip>:12000`. Set the address
|
||||
explicitly to bypass auto-detection:
|
||||
|
||||
```bash
|
||||
FLOCI_SERVICES_LAMBDA_DOCKER_HOST_OVERRIDE=floci
|
||||
```
|
||||
|
||||
This forces every spawned Lambda container to reach the Runtime API at the
|
||||
given host (here the `FLOCI_HOSTNAME` value), skipping Floci's
|
||||
auto-detection entirely. See the [Lambda docs](../services/lambda.md#configuration)
|
||||
for details.
|
||||
|
||||
## Full Reference
|
||||
|
||||
| Environment variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_DOCKER_DOCKER_HOST` | `unix:///var/run/docker.sock` | Docker daemon socket |
|
||||
| `FLOCI_DOCKER_DOCKER_CONFIG_PATH` | _(unset)_ | Path to directory containing Docker's `config.json` |
|
||||
| `FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__SERVER` | _(unset)_ | Registry hostname for credential entry 0 |
|
||||
| `FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__USERNAME` | _(unset)_ | Username for credential entry 0 |
|
||||
| `FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__PASSWORD` | _(unset)_ | Password for credential entry 0 |
|
||||
| `FLOCI_DOCKER_LOG_MAX_SIZE` | `10m` | Max container log file size before rotation |
|
||||
| `FLOCI_DOCKER_LOG_MAX_FILE` | `3` | Number of rotated log files to retain |
|
||||
| `FLOCI_DOCKER_EXTRA_LABELS_0__KEY` | _(unset)_ | Label key for extra-label entry 0, applied to every Floci-created container and volume (increment the index for more) |
|
||||
| `FLOCI_DOCKER_EXTRA_LABELS_0__VALUE` | _(unset)_ | Label value for extra-label entry 0 |
|
||||
| `FLOCI_SERVICES_DOCKER_NETWORK` | _(unset)_ | Shared Docker network for all container-based services |
|
||||
| `FLOCI_SERVICES_LAMBDA_CONTAINER_NAME_PREFIX` | `floci` | Base name prefix for spawned Lambda containers and code volumes (e.g. `acme` → `acme-<function>-<id>` containers, `acme-code-<function>-<hash>` volumes). Must be a valid Docker name segment (`[A-Za-z0-9][A-Za-z0-9_.-]*`); invalid values are ignored with a warning. See the [Lambda docs](../services/lambda.md#configuration) |
|
||||
@@ -0,0 +1,556 @@
|
||||
# Environment Variables Reference
|
||||
|
||||
Floci is configured exclusively through environment variables. Every option below maps directly to a `FLOCI_*` variable — no YAML file is needed when running the published Docker image.
|
||||
|
||||
---
|
||||
|
||||
## Global
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_BASE_URL` | `http://localhost:4566` | Base URL embedded in response fields (SQS `QueueUrl`, pre-signed URLs, etc.) |
|
||||
| `FLOCI_HOSTNAME` | _(none)_ | Overrides only the hostname part of `FLOCI_BASE_URL`. Set to the Compose service name (e.g. `floci`) so other containers can reach Floci by DNS |
|
||||
| `FLOCI_DEFAULT_REGION` | `us-east-1` | AWS region used in ARNs and API responses |
|
||||
| `FLOCI_DEFAULT_ACCOUNT_ID` | `000000000000` | Fallback account ID used in ARNs when the request's access key is not exactly 12 digits. When the access key IS 12 digits, it is used directly as the account ID — see [Multi-Account Isolation](./multi-account.md) |
|
||||
| `FLOCI_DEFAULT_AVAILABILITY_ZONE` | `us-east-1a` | Availability zone reported in EC2 and other responses |
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_AUTH_VALIDATE_SIGNATURES` | `false` | When `true`, verifies S3 presigned URL signatures |
|
||||
| `FLOCI_AUTH_PRESIGN_SECRET` | `local-emulator-secret` | Secret used to sign and verify pre-signed URLs |
|
||||
|
||||
## Network Exposure
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `QUARKUS_HTTP_HOST` | `127.0.0.1` | Address Floci listens on. With TLS enabled, the proxy serving HTTP and HTTPS on `FLOCI_PORT` listens here |
|
||||
| `FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE` | `false` | Allow listening outside loopback (`127.0.0.0/8`, `::1`, `localhost`). Without it, Floci refuses to start on any other address |
|
||||
|
||||
Anyone who can reach Floci's port can call its APIs. The Docker images listen on `0.0.0.0` inside the container and pass both settings in their default command, so who can reach Floci depends on how you publish the port. Publish it on loopback unless other machines need it:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 127.0.0.1:4566:4566 floci/floci:latest
|
||||
```
|
||||
|
||||
Running Floci directly on a Linux host (not in a container) with services that start containers, such as Lambda functions or ECS tasks, needs a non-loopback address. Those containers reach Floci through `host.docker.internal`, which resolves to the Docker bridge gateway (`172.17.0.1` by default) rather than to the host's loopback. Set `QUARKUS_HTTP_HOST=0.0.0.0` and `FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE=true`, and keep port 4566 closed to other networks with a firewall. See also [Lambda on native Linux Docker](../getting-started/quick-start.md#lambda-on-native-linux-docker-ufw).
|
||||
|
||||
## Browser CORS
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SECURITY_EXTRA_CORS_ALLOWED_ORIGINS` | _(none)_ | Comma-separated browser origins allowed to call Floci directly. Alias: `EXTRA_CORS_ALLOWED_ORIGINS` |
|
||||
| `FLOCI_SECURITY_EXTRA_CORS_ALLOWED_HEADERS` | _(none)_ | Additional header names to include in `Access-Control-Allow-Headers`. Alias: `EXTRA_CORS_ALLOWED_HEADERS` |
|
||||
| `FLOCI_SECURITY_EXTRA_CORS_EXPOSE_HEADERS` | _(none)_ | Additional header names to include in `Access-Control-Expose-Headers`. Alias: `EXTRA_CORS_EXPOSE_HEADERS` |
|
||||
| `FLOCI_SECURITY_DISABLE_CORS_HEADERS` | `false` | Disable Floci's global CORS response headers. Alias: `DISABLE_CORS_HEADERS` |
|
||||
| `FLOCI_SECURITY_CORS_ALLOW_PRIVATE_NETWORK` | `false` | Answer Private Network Access preflights with `Access-Control-Allow-Private-Network: true`, letting a page on a public/secure origin reach this loopback backend. Only applies after the origin passes the allow-list above. |
|
||||
|
||||
---
|
||||
|
||||
## TLS / HTTPS
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_TLS_ENABLED` | `false` | Enable TLS/HTTPS on all endpoints (HTTP remains available simultaneously) |
|
||||
| `FLOCI_TLS_CERT_PATH` | _(none)_ | Path to a PEM certificate file. When set, disables auto-generation |
|
||||
| `FLOCI_TLS_KEY_PATH` | _(none)_ | Path to a PEM private key file. Required when `FLOCI_TLS_CERT_PATH` is set |
|
||||
| `FLOCI_TLS_SELF_SIGNED` | `true` | Auto-generate and persist a server certificate signed by Floci's local CA when no cert/key paths are provided |
|
||||
|
||||
See [TLS / HTTPS](./tls.md) for SDK configuration examples and WebSocket (`wss://`) support.
|
||||
|
||||
---
|
||||
|
||||
## Wire Protocols
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_PROTOCOLS_MAX_REQUEST_SIZE` | `2048` | Maximum HTTP request body size in megabytes (feeds `quarkus.http.limits.max-body-size`). Legacy name `FLOCI_MAX_REQUEST_SIZE` still works |
|
||||
| `FLOCI_PROTOCOLS_STRICT_CLAIMING` | `false` | Reject RPC-signaled requests that no supported wire protocol claims, per the [Smithy wire-protocol-selection guide](https://smithy.io/2.0/guides/wire-protocol-selection.html) (e.g. an unknown `Smithy-Protocol` header value or an unimplemented `rpc-v2-json` request). When disabled such requests are logged and pass through |
|
||||
| `FLOCI_PROTOCOLS_REJECT_UNKNOWN_SERVICE_SCOPE` | `true` | Reject REST requests whose SigV4 credential scope names a service Floci does not implement, with `UnknownOperationException` instead of letting them fall through to S3's path-style routes and return a misleading `NoSuchBucket`. Set to `false` if Floci serves a route whose signing scope is not yet enumerated: the request then falls through as before instead of failing with a 404 |
|
||||
|
||||
---
|
||||
|
||||
## Storage
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_STORAGE_MODE` | `memory` | Global storage backend: `memory`, `persistent`, `hybrid`, or `wal` |
|
||||
| `FLOCI_STORAGE_PERSISTENT_PATH` | `./data` | Container-side directory for persistent and hybrid storage |
|
||||
| `FLOCI_STORAGE_HOST_PERSISTENT_PATH` | `./data` | Host-side path for Docker volume bind-mounts (RDS, OpenSearch, MSK, ECR data). When unset, Floci uses named Docker volumes |
|
||||
| `FLOCI_STORAGE_PRUNE_VOLUMES_ON_DELETE` | `false` | Remove named Docker volumes immediately when the resource is deleted |
|
||||
| `FLOCI_STORAGE_WAL_COMPACTION_INTERVAL_MS` | `30000` | How often (ms) the WAL compaction runs. Only applies when `FLOCI_STORAGE_MODE=wal` |
|
||||
|
||||
### Per-service storage overrides
|
||||
|
||||
Each service can override the global storage mode and flush interval. Replace `<SERVICE>` with the uppercase service name:
|
||||
|
||||
```
|
||||
FLOCI_STORAGE_SERVICES_<SERVICE>_MODE=hybrid
|
||||
FLOCI_STORAGE_SERVICES_<SERVICE>_FLUSH_INTERVAL_MS=5000
|
||||
```
|
||||
|
||||
Available service names: `SSM`, `SQS`, `S3`, `DYNAMODB`, `SNS`, `LAMBDA`, `CLOUDWATCHLOGS`, `CLOUDWATCHMETRICS`, `SECRETSMANAGER`, `ACM`, `OPENSEARCH`, `RDS`, `ELASTICACHE`, `APPCONFIG`, `APPCONFIGDATA`, `BACKUP`, `FIS`.
|
||||
|
||||
See [Storage Modes](./storage.md) for a full explanation of each mode.
|
||||
|
||||
---
|
||||
|
||||
## Docker Daemon
|
||||
|
||||
This foundation release provides opt-in security-group filtering for EC2 Docker instances and ECS `awsvpc` tasks. A rootful Linux Docker daemon with nftables support is required. Before enabling it, terminate existing EC2 instances and ECS tasks, then launch replacements so their namespaces are prepared before application code starts. Restart Floci after changing this setting. Mock mode remains a control-plane simulation and does not filter packets.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_NETWORK_SECURITY_GROUP_ENFORCEMENT_ENABLED` | `false` | Opt in to filtering Docker-backed EC2 and ECS `awsvpc` traffic by attached security groups |
|
||||
| `FLOCI_NETWORK_SECURITY_GROUP_ENFORCEMENT_HELPER_IMAGE` | `floci/network-helper:local` | Linux helper image containing nftables; Floci builds the default image locally when missing |
|
||||
| `FLOCI_DOCKER_DOCKER_HOST` | `unix:///var/run/docker.sock` | Docker daemon socket path or TCP address |
|
||||
| `FLOCI_DOCKER_DOCKER_CONFIG_PATH` | _(none)_ | Path to a directory containing Docker's `config.json` for registry auth |
|
||||
| `FLOCI_DOCKER_IMAGE_REGISTRY_BASE` | _(none)_ | Optional registry/repository base for every Docker image Floci launches. When set, `postgres:16-alpine` resolves as `<base>/postgres:16-alpine` and `public.ecr.aws/docker/library/ubuntu:24.04` resolves as `<base>/public.ecr.aws/docker/library/ubuntu:24.04` |
|
||||
| `FLOCI_DOCKER_LOG_MAX_SIZE` | `10m` | Log rotation max size for spawned containers (e.g. `10m`, `1g`) |
|
||||
| `FLOCI_DOCKER_LOG_MAX_FILE` | `3` | Number of rotated log files to keep for spawned containers |
|
||||
| `FLOCI_DOCKER_RESOURCE_NAMESPACE` | _(none)_ | Optional namespace prefix for managed child Docker container and volume names |
|
||||
| `FLOCI_DOCKER_EXTRA_LABELS_0__KEY` | _(none)_ | Label key for extra-label entry 0, applied to every Floci-created container and volume (increment the index for more) |
|
||||
| `FLOCI_DOCKER_EXTRA_LABELS_0__VALUE` | _(none)_ | Label value for extra-label entry 0 |
|
||||
|
||||
### Registry credentials
|
||||
|
||||
Provide credentials for private registries (e.g. for Lambda base images). Use incrementing indexes (`0`, `1`, `2`, …) for multiple registries:
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__SERVER` | Registry hostname (e.g. `ghcr.io`) |
|
||||
| `FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__USERNAME` | Registry username |
|
||||
| `FLOCI_DOCKER_REGISTRY_CREDENTIALS_0__PASSWORD` | Registry password or token |
|
||||
|
||||
---
|
||||
|
||||
## DNS
|
||||
|
||||
Floci's embedded DNS server always resolves the following wildcard suffixes to Floci's container IP — no configuration required:
|
||||
|
||||
| Built-in suffix | Covers |
|
||||
|---|---|
|
||||
| `localhost.floci.io` | `localhost.floci.io` and `*.localhost.floci.io` (e.g. `my-bucket.s3.localhost.floci.io`) |
|
||||
| `localhost.localstack.cloud` | `localhost.localstack.cloud` and `*.localhost.localstack.cloud` (e.g. `my-bucket.s3.localhost.localstack.cloud`) |
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_DNS_EXTRA_SUFFIXES` | _(none)_ | Comma-separated list of additional hostname suffixes to resolve to Floci's container IP. Use this for custom domains beyond the built-in ones above (e.g. a private internal suffix). |
|
||||
|
||||
---
|
||||
|
||||
## Initialization Hooks
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_INIT_HOOKS_SHELL_EXECUTABLE` | `/bin/sh` | Shell used to execute hook scripts |
|
||||
| `FLOCI_INIT_HOOKS_TIMEOUT_SECONDS` | `30` | Maximum time a single hook script may run |
|
||||
| `FLOCI_INIT_HOOKS_SHUTDOWN_GRACE_PERIOD_SECONDS` | `2` | Time allowed for stop hooks to complete during shutdown |
|
||||
|
||||
See [Initialization Hooks](./initialization-hooks.md) for lifecycle phases and script conventions.
|
||||
|
||||
---
|
||||
|
||||
## Services — Shared
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_DOCKER_NETWORK` | _(none)_ | Docker network name used by all container-backed services (Lambda, RDS, ElastiCache, ECS, OpenSearch, EKS, MSK). Per-service overrides take precedence |
|
||||
|
||||
---
|
||||
|
||||
## Services — Core
|
||||
|
||||
### SSM (Parameter Store)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_SSM_ENABLED` | `true` | Enable the SSM service |
|
||||
| `FLOCI_SERVICES_SSM_MAX_PARAMETER_HISTORY` | `5` | Maximum number of historical versions kept per parameter |
|
||||
|
||||
### SQS
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_SQS_ENABLED` | `true` | Enable the SQS service |
|
||||
| `FLOCI_SERVICES_SQS_DEFAULT_VISIBILITY_TIMEOUT` | `30` | Default message visibility timeout in seconds |
|
||||
| `FLOCI_SERVICES_SQS_MAX_MESSAGE_SIZE` | `1048576` | Maximum message body size in bytes (1 MiB) |
|
||||
| `FLOCI_SERVICES_SQS_CLEAR_FIFO_DEDUPLICATION_CACHE_ON_PURGE` | `false` | Reset the deduplication cache when a FIFO queue is purged |
|
||||
|
||||
### SNS
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_SNS_ENABLED` | `true` | Enable the SNS service |
|
||||
|
||||
### S3
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_S3_ENABLED` | `true` | Enable the S3 service |
|
||||
| `FLOCI_SERVICES_S3_DEFAULT_PRESIGN_EXPIRY_SECONDS` | `3600` | Default pre-signed URL expiry when none is specified |
|
||||
| `FLOCI_SERVICES_S3_GLOBAL_BUCKET_NAMESPACE` | `false` | When `true`, bucket/object resolution spans every account's partition so a bucket created in one account is visible cross-account (a single global namespace), matching real S3's global bucket names. Off by default keeps buckets isolated per account |
|
||||
|
||||
### DynamoDB
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_DYNAMODB_ENABLED` | `true` | Enable the DynamoDB service |
|
||||
|
||||
### Lambda
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_LAMBDA_ENABLED` | `true` | Enable the Lambda service |
|
||||
| `FLOCI_SERVICES_LAMBDA_EPHEMERAL` | `false` | Remove Lambda containers immediately after each invocation |
|
||||
| `FLOCI_SERVICES_LAMBDA_ECR_BASE_URI` | `public.ecr.aws` | Registry (optionally with a path prefix) the Lambda runtime images are pulled from, e.g. `public.ecr.aws/lambda/python:3.12`. Legacy name `FLOCI_ECR_BASE_URI` still works |
|
||||
| `FLOCI_SERVICES_LAMBDA_DEFAULT_MEMORY_MB` | `128` | Default memory allocation for functions that don't specify one |
|
||||
| `FLOCI_SERVICES_LAMBDA_DEFAULT_TIMEOUT_SECONDS` | `3` | Default invocation timeout in seconds |
|
||||
| `FLOCI_SERVICES_LAMBDA_RUNTIME_API_BASE_PORT` | `12000` | First port in the Lambda Runtime API port range |
|
||||
| `FLOCI_SERVICES_LAMBDA_RUNTIME_API_MAX_PORT` | `12499` | Last port in the Lambda Runtime API port range. One port is held per running Lambda container, so the range width is the concurrent-execution ceiling |
|
||||
| `FLOCI_SERVICES_LAMBDA_CODE_PATH` | `./data/lambda-code` | Container path where Lambda deployment ZIPs are stored |
|
||||
| `FLOCI_SERVICES_LAMBDA_POLL_INTERVAL_MS` | `1000` | How often (ms) the SQS and Kinesis event source pollers check for new messages |
|
||||
| `FLOCI_SERVICES_LAMBDA_CONTAINER_IDLE_TIMEOUT_SECONDS` | `300` | Seconds of inactivity before an idle Lambda container is removed |
|
||||
| `FLOCI_SERVICES_LAMBDA_REGION_CONCURRENCY_LIMIT` | `1000` | Maximum concurrent Lambda invocations across all functions in a region |
|
||||
| `FLOCI_SERVICES_LAMBDA_UNRESERVED_CONCURRENCY_MIN` | `100` | Minimum unreserved concurrency pool |
|
||||
| `FLOCI_SERVICES_LAMBDA_CODE_VOLUME_POPULATE_CONCURRENCY` | `max(2, cpus/2)` | Maximum concurrent first-time code-volume populates (functions whose unpacked code is at least 32 MB). The default is derived from the CPU count the JVM sees, so a CPU-constrained Floci container collapses it to 2 and concurrent cold starts of distinct functions serialise into pairs; set this to decouple the cap from the CPU allocation |
|
||||
| `FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ENABLED` | `false` | Watch Lambda code directories for changes and reload without redeployment |
|
||||
| `FLOCI_SERVICES_LAMBDA_HOT_RELOAD_ALLOWED_PATHS` | _(none)_ | Comma-separated host paths that hot-reload is allowed to watch |
|
||||
| `FLOCI_SERVICES_LAMBDA_DOCKER_NETWORK` | _(none)_ | Docker network for Lambda containers (overrides `FLOCI_SERVICES_DOCKER_NETWORK`) |
|
||||
| `FLOCI_SERVICES_LAMBDA_DOCKER_FLAGS` | _(none)_ | Additional Docker flags applied to Lambda containers, such as `--env`, `--volume`, `--publish`, `--add-host`, `--dns`, `--label`, `--network`, `--user`, `--privileged`, and `--platform`. Published ports support `host:container` and `127.0.0.1:host:container` forms |
|
||||
| `FLOCI_SERVICES_LAMBDA_CONTAINER_NAME_PREFIX` | `floci` | Base name prefix for Lambda-spawned containers and code volumes (must match `[A-Za-z0-9][A-Za-z0-9_.-]*`) |
|
||||
| `FLOCI_SERVICES_LAMBDA_DOCKER_HOST_OVERRIDE` | _(none)_ | Explicit host/IP Lambda containers use to reach the Runtime API, bypassing auto-detection (e.g. rootless Podman) |
|
||||
| `FLOCI_SERVICES_LAMBDA_AWS_CONFIG_PATH` | _(none)_ | Host path bind-mounted read-only at `/opt/aws-config` inside Lambda containers for real credential discovery |
|
||||
| `FLOCI_SERVICES_LAMBDA_EXECUTOR` | `docker` | Execution backend for Lambda environments: `docker` (containers) or `kubernetes` (pods) |
|
||||
| `FLOCI_SERVICES_LAMBDA_KUBERNETES_NAMESPACE` | `default` | Namespace Lambda pods are created in |
|
||||
| `FLOCI_SERVICES_LAMBDA_KUBERNETES_LABELS` | _(none)_ | Extra pod labels as comma-separated `key=value` entries |
|
||||
| `FLOCI_SERVICES_LAMBDA_KUBERNETES_FLOCI_ADDRESS` | _(none)_ | Host/IP Lambda pods use to reach Floci; auto-detected when Floci runs in-cluster |
|
||||
| `FLOCI_SERVICES_LAMBDA_KUBERNETES_INIT_IMAGE` | `busybox:1.36` | Init-container image that downloads function code into the pod (needs `sh`, `wget`, `unzip`) |
|
||||
|
||||
### API Gateway
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_APIGATEWAY_ENABLED` | `true` | Enable the API Gateway v1 (REST) service |
|
||||
| `FLOCI_SERVICES_APIGATEWAYV2_ENABLED` | `true` | Enable the API Gateway v2 (HTTP + WebSocket) service |
|
||||
|
||||
### IAM
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_IAM_ENABLED` | `true` | Enable the IAM service |
|
||||
| `FLOCI_SERVICES_IAM_ENFORCEMENT_ENABLED` | `false` | When `true`, enforce IAM policies on API calls. Leave `false` for most local development scenarios |
|
||||
| `FLOCI_SERVICES_IAM_SEED_DEPLOYER_PRINCIPAL` | `false` | Create a local `floci-deployer` IAM user with `AdministratorAccess` and static `floci`/`floci` credentials |
|
||||
|
||||
### KMS
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_KMS_ENABLED` | `true` | Enable the KMS service |
|
||||
|
||||
### Kinesis
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_KINESIS_ENABLED` | `true` | Enable the Kinesis Data Streams service |
|
||||
|
||||
### Firehose
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_FIREHOSE_ENABLED` | `true` | Enable the Kinesis Data Firehose service |
|
||||
| `FLOCI_SERVICES_FIREHOSE_TICK_INTERVAL_SECONDS` | `10` | How often (seconds) the buffer flusher checks for streams whose `BufferingHints.IntervalInSeconds` has elapsed |
|
||||
| `FLOCI_SERVICES_FIREHOSE_FLUSH_RECORD_COUNT` | `0` | Emulator-only: flush after this many buffered records (`0` = disabled, AWS-faithful; `1` = LocalStack-style record-at-a-time delivery) |
|
||||
| `FLOCI_SERVICES_FIREHOSE_STAGING_BUCKET` | `floci-firehose-staging` | S3 bucket used to stage NDJSON batches before DuckDB writes the Parquet object for format-converting streams |
|
||||
|
||||
### EventBridge
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_EVENTBRIDGE_ENABLED` | `true` | Enable the EventBridge service |
|
||||
|
||||
### Scheduler
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_SCHEDULER_ENABLED` | `true` | Enable the EventBridge Scheduler service |
|
||||
| `FLOCI_SERVICES_SCHEDULER_INVOCATION_ENABLED` | `true` | When `false`, schedules are stored but never invoked |
|
||||
| `FLOCI_SERVICES_SCHEDULER_TICK_INTERVAL_SECONDS` | `10` | How often (seconds) the scheduler checks for due schedules |
|
||||
|
||||
### CloudWatch Logs
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_CLOUDWATCHLOGS_ENABLED` | `true` | Enable the CloudWatch Logs service |
|
||||
| `FLOCI_SERVICES_CLOUDWATCHLOGS_MAX_EVENTS_PER_QUERY` | `10000` | Maximum log events returned by a single `FilterLogEvents` or `GetLogEvents` call, and the upper bound for a Logs Insights `limit` |
|
||||
| `FLOCI_SERVICES_CLOUDWATCHLOGS_MAX_STORED_EVENTS` | `20000` | Maximum log events retained per account across all groups; oldest events are evicted first |
|
||||
| `FLOCI_SERVICES_CLOUDWATCHLOGS_QUERY_COMPLETION_DELAY_MS` | `0` | Artificial Logs Insights query delay. With `0` a query completes immediately; a positive value emulates the asynchronous `Running` → `Complete` lifecycle |
|
||||
|
||||
### CloudWatch Metrics
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_CLOUDWATCHMETRICS_ENABLED` | `true` | Enable the CloudWatch Metrics service |
|
||||
|
||||
### Secrets Manager
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_SECRETSMANAGER_ENABLED` | `true` | Enable the Secrets Manager service |
|
||||
| `FLOCI_SERVICES_SECRETSMANAGER_DEFAULT_RECOVERY_WINDOW_DAYS` | `30` | Default recovery window for deleted secrets |
|
||||
|
||||
### Cognito
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_COGNITO_ENABLED` | `true` | Enable the Cognito User Pools service |
|
||||
|
||||
### Step Functions
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_STEPFUNCTIONS_ENABLED` | `true` | Enable the Step Functions service |
|
||||
|
||||
### CloudFormation
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_CLOUDFORMATION_ENABLED` | `true` | Enable the CloudFormation service |
|
||||
| `FLOCI_SERVICES_CLOUDFORMATION_ALLOW_STUB_LAMBDA_CODE` | `false` | Fall back to the built-in stub handler for an `AWS::Lambda::Function` whose S3 code cannot be read. Off matches real CloudFormation, which fails the resource and rolls the stack back |
|
||||
| `FLOCI_SERVICES_CLOUDFORMATION_ALLOW_STUB_UNSUPPORTED_RESOURCE_TYPES` | `true` | Stub a resource whose type has no provisioner (synthetic physical ID, `arn:aws:stub:::` ARN attribute, `CREATE_COMPLETE`), logged at `WARN` with a resource status reason. Set `false` to fail the resource instead, which rolls the stack back |
|
||||
|
||||
### ACM (Certificate Manager)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_ACM_ENABLED` | `true` | Enable the ACM service |
|
||||
| `FLOCI_SERVICES_ACM_VALIDATION_WAIT_SECONDS` | `0` | Simulated delay before a requested certificate transitions to `ISSUED` |
|
||||
|
||||
### SES (Simple Email Service)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_SES_ENABLED` | `true` | Enable the SES service |
|
||||
| `FLOCI_SERVICES_SES_SMTP_HOST` | _(none)_ | SMTP relay host for outbound email. When unset, emails are captured in memory only |
|
||||
| `FLOCI_SERVICES_SES_SMTP_PORT` | `25` | SMTP relay port |
|
||||
| `FLOCI_SERVICES_SES_SMTP_USER` | _(none)_ | SMTP username |
|
||||
| `FLOCI_SERVICES_SES_SMTP_PASS` | _(none)_ | SMTP password |
|
||||
| `FLOCI_SERVICES_SES_SMTP_STARTTLS` | `DISABLED` | STARTTLS mode: `DISABLED`, `OPTIONAL`, or `REQUIRED` |
|
||||
|
||||
### Pipes
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_PIPES_ENABLED` | `true` | Enable the EventBridge Pipes service |
|
||||
|
||||
### IoT Core
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_IOT_ENABLED` | `true` | Enable the IoT Core service |
|
||||
| `FLOCI_SERVICES_IOT_ENDPOINT_ADDRESS` | _(none)_ | Value `DescribeEndpoint` returns for every endpoint type, and the domain name of the AWS-managed domain configurations. Set it to a bare hostname when the AWS IoT ports (8883, 443, 8443) reach Floci; the name is added to the server certificate. Defaults to the host and port of `FLOCI_BASE_URL`, with `FLOCI_HOSTNAME` applied |
|
||||
| `FLOCI_SERVICES_IOT_RULE_SQL_STRICT` | `false` | Reject topic rules whose SQL falls outside the evaluated subset, as AWS does |
|
||||
| `FLOCI_SERVICES_IOT_MQTT_ENABLED` | `true` | Run the embedded MQTT broker |
|
||||
| `FLOCI_SERVICES_IOT_MQTT_AUTO_START` | `false` | Start the broker at boot instead of on the first IoT API call |
|
||||
| `FLOCI_SERVICES_IOT_MQTT_HOST` | `0.0.0.0` | Address the broker listens on |
|
||||
| `FLOCI_SERVICES_IOT_MQTT_PORT` | `1883` | Plaintext MQTT port |
|
||||
| `FLOCI_SERVICES_IOT_MQTT_TLS_PORT` | `8883` | MQTT over TLS port, opened while `FLOCI_TLS_ENABLED` is `true`; `0` disables it |
|
||||
|
||||
---
|
||||
|
||||
## Services — Container-Backed
|
||||
|
||||
These services spawn Docker containers. They require access to the Docker socket (`/var/run/docker.sock`).
|
||||
|
||||
### ElastiCache
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_ELASTICACHE_ENABLED` | `true` | Enable the ElastiCache service |
|
||||
| `FLOCI_SERVICES_ELASTICACHE_PROXY_BASE_PORT` | `6379` | First port in the ElastiCache proxy range |
|
||||
| `FLOCI_SERVICES_ELASTICACHE_PROXY_MAX_PORT` | `6399` | Last port in the ElastiCache proxy range |
|
||||
| `FLOCI_SERVICES_ELASTICACHE_DEFAULT_IMAGE` | `valkey/valkey:8` | Default Docker image for cache clusters |
|
||||
| `FLOCI_SERVICES_ELASTICACHE_DOCKER_NETWORK` | _(none)_ | Docker network for ElastiCache containers (overrides `FLOCI_SERVICES_DOCKER_NETWORK`) |
|
||||
| `FLOCI_SERVICES_ELASTICACHE_CLUSTER_ANNOUNCE_HOSTNAME` | _(none)_ | Hostname cluster-mode nodes announce in `MOVED`/`ASK` redirects and topology responses, and report as the `ConfigurationEndpoint`. Set to a name every client resolves (e.g. `localhost.floci.io`) when `FLOCI_HOSTNAME` only resolves inside Floci's Docker network. Defaults to `FLOCI_HOSTNAME` |
|
||||
|
||||
### RDS
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_RDS_ENABLED` | `true` | Enable the RDS service |
|
||||
| `FLOCI_SERVICES_RDS_MOCK` | `false` | When `true`, DB clusters and instances are created instantly without a real container or auth proxy (API only) |
|
||||
| `FLOCI_SERVICES_RDS_PROXY_BASE_PORT` | `7001` | First port in the RDS proxy range |
|
||||
| `FLOCI_SERVICES_RDS_PROXY_MAX_PORT` | `7099` | Last port in the RDS proxy range |
|
||||
| `FLOCI_SERVICES_RDS_ENDPOINT_HOST` | _(auto-detected)_ | Hostname advertised in RDS endpoints; when set in Docker, Floci advertises each proxy's published host port |
|
||||
| `FLOCI_SERVICES_RDS_DEFAULT_POSTGRES_IMAGE` | `postgres:16-alpine` | Default PostgreSQL Docker image |
|
||||
| `FLOCI_SERVICES_RDS_DEFAULT_MYSQL_IMAGE` | `mysql:8.0` | Default MySQL Docker image |
|
||||
| `FLOCI_SERVICES_RDS_DEFAULT_MARIADB_IMAGE` | `mariadb:11` | Default MariaDB Docker image |
|
||||
| `FLOCI_SERVICES_RDS_DEFAULT_SQL_SERVER_IMAGE` | `mcr.microsoft.com/mssql/server:2022-latest` | Default SQL Server Docker image |
|
||||
| `FLOCI_SERVICES_RDS_DOCKER_NETWORK` | _(none)_ | Docker network for RDS containers (overrides `FLOCI_SERVICES_DOCKER_NETWORK`) |
|
||||
| `FLOCI_SERVICES_RDS_DATA_ENABLED` | `true` | Enable the RDS Data API service. Requires `FLOCI_SERVICES_RDS_ENABLED=true` |
|
||||
| `FLOCI_SERVICES_RDS_DATA_TRANSACTION_TTL_SECONDS` | `180` | Idle timeout, in seconds, before leaked RDS Data API transactions expire |
|
||||
|
||||
### OpenSearch
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_OPENSEARCH_ENABLED` | `true` | Enable the OpenSearch service |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_MOCK` | `false` | When `true`, domains are created instantly without a real container (API only) |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_DEFAULT_IMAGE` | *(unset)* | Optional fixed Docker image for every OpenSearch domain; when unset, images resolve per requested `EngineVersion` |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_PROXY_BASE_PORT` | `9400` | First port in the OpenSearch proxy range |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_PROXY_MAX_PORT` | `9499` | Last port in the OpenSearch proxy range |
|
||||
| `FLOCI_SERVICES_OPENSEARCH_KEEP_RUNNING_ON_SHUTDOWN` | `false` | Keep OpenSearch containers running when Floci stops |
|
||||
|
||||
### MSK (Managed Streaming for Kafka)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_MSK_ENABLED` | `true` | Enable the MSK service |
|
||||
| `FLOCI_SERVICES_MSK_MOCK` | `false` | When `true`, clusters are created instantly without a real Redpanda container |
|
||||
| `FLOCI_SERVICES_MSK_DEFAULT_IMAGE` | `redpandadata/redpanda:latest` | Docker image for Kafka/Redpanda brokers |
|
||||
|
||||
### Amazon MQ
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_AMAZONMQ_ENABLED` | `true` | Enable the Amazon MQ service |
|
||||
| `FLOCI_SERVICES_AMAZONMQ_MOCK` | `false` | When `true`, brokers are created instantly without a real RabbitMQ container |
|
||||
| `FLOCI_SERVICES_AMAZONMQ_DEFAULT_IMAGE` | `rabbitmq:3-management` | Docker image for RabbitMQ broker containers |
|
||||
| `FLOCI_SERVICES_AMAZONMQ_AMQP_HOST_PORT_BASE` | `5672` | First host port in the range the AMQP listener is published on |
|
||||
| `FLOCI_SERVICES_AMAZONMQ_AMQP_HOST_PORT_MAX` | `5699` | Last host port in the AMQP range |
|
||||
| `FLOCI_SERVICES_AMAZONMQ_CONSOLE_HOST_PORT_BASE` | `15672` | First host port in the range the management console is published on |
|
||||
| `FLOCI_SERVICES_AMAZONMQ_CONSOLE_HOST_PORT_MAX` | `15699` | Last host port in the console range |
|
||||
|
||||
### Managed Service for Apache Flink (Kinesis Analytics V2)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_KINESIS_ANALYTICS_ENABLED` | `true` | Enable the Managed Flink (Kinesis Analytics V2) service |
|
||||
| `FLOCI_SERVICES_KINESIS_ANALYTICS_MOCK` | `false` | When `true`, applications start instantly without a real Flink container |
|
||||
| `FLOCI_SERVICES_KINESIS_ANALYTICS_DEFAULT_IMAGE` | _(unset)_ | Optional image override; when unset, the image is chosen from the requested `RuntimeEnvironment` (e.g. `FLINK-1_19` → `apache/flink:1.19`) |
|
||||
|
||||
### ECR (Elastic Container Registry)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_ECR_ENABLED` | `true` | Enable the ECR service |
|
||||
| `FLOCI_SERVICES_ECR_REGISTRY_IMAGE` | `registry:2` | Docker image for the ECR registry sidecar |
|
||||
| `FLOCI_SERVICES_ECR_REGISTRY_CONTAINER_NAME` | `floci-ecr-registry` | Name of the ECR registry sidecar container |
|
||||
| `FLOCI_SERVICES_ECR_REGISTRY_BASE_PORT` | `5100` | First private loopback port for the backing registry |
|
||||
| `FLOCI_SERVICES_ECR_REGISTRY_MAX_PORT` | `5199` | Last private loopback port for the backing registry |
|
||||
| `FLOCI_SERVICES_ECR_TLS_ENABLED` | `false` | Enable TLS for the ECR registry |
|
||||
| `FLOCI_SERVICES_ECR_KEEP_RUNNING_ON_SHUTDOWN` | `true` | Keep the ECR registry container running when Floci stops |
|
||||
| `FLOCI_SERVICES_ECR_URI_STYLE` | `hostname` | Repository URI style: `hostname` (`<account>.dkr.ecr.<region>.localhost`) or `path` |
|
||||
| `FLOCI_SERVICES_ECR_PREFER_LOCAL_IMAGES` | `true` | Use an AWS-shaped ECR image URI as-is when the Docker daemon already has that image, instead of rewriting it to the loopback registry |
|
||||
| `FLOCI_SERVICES_ECR_DOCKER_NETWORK` | _(none)_ | Docker network for the ECR registry container |
|
||||
|
||||
### EKS (Elastic Kubernetes Service)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_EKS_ENABLED` | `true` | Enable the EKS service |
|
||||
| `FLOCI_SERVICES_EKS_MOCK` | `false` | When `true`, clusters are created instantly without a real container |
|
||||
| `FLOCI_SERVICES_EKS_PROVIDER` | `k3s` | Kubernetes provider (`k3s`) |
|
||||
| `FLOCI_SERVICES_EKS_DEFAULT_IMAGE` | `rancher/k3s:latest` | Docker image for EKS clusters |
|
||||
| `FLOCI_SERVICES_EKS_IMAGE_TEMPLATE` | _(none)_ | Format string for custom k3s images (e.g. `myrepo/k3s:v%s`), taking cluster version |
|
||||
| `FLOCI_SERVICES_EKS_API_SERVER_BASE_PORT` | `6500` | First port in the Kubernetes API server range |
|
||||
| `FLOCI_SERVICES_EKS_API_SERVER_MAX_PORT` | `6599` | Last port in the Kubernetes API server range |
|
||||
| `FLOCI_SERVICES_EKS_KEEP_RUNNING_ON_SHUTDOWN` | `false` | Keep EKS containers running when Floci stops |
|
||||
| `FLOCI_SERVICES_EKS_DOCKER_NETWORK` | _(none)_ | Docker network for EKS containers |
|
||||
|
||||
### ECS (Elastic Container Service)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_ECS_ENABLED` | `true` | Enable the ECS service |
|
||||
| `FLOCI_SERVICES_ECS_MOCK` | `false` | When `true`, tasks are registered but not actually run |
|
||||
| `FLOCI_SERVICES_ECS_PUBLISH_AWSVPC_PORTS_TO_HOST` | `false` | Publish `awsvpc` task ports on stable Docker host ports for host-side clients |
|
||||
| `FLOCI_SERVICES_ECS_DEFAULT_MEMORY_MB` | `512` | Default task memory when not specified in the task definition |
|
||||
| `FLOCI_SERVICES_ECS_DEFAULT_CPU_UNITS` | `256` | Default task CPU units when not specified in the task definition |
|
||||
| `FLOCI_SERVICES_ECS_DOCKER_NETWORK` | _(none)_ | Docker network for ECS task containers |
|
||||
| `FLOCI_SERVICES_ECS_HOST_VOLUME_ROOTS` | _(none)_ | Comma-separated allowlist of parent directories host volume `sourcePath`s must resolve under. By default (unset, and `ALLOW_UNSAFE_HOST_VOLUMES=false`) every host volume `sourcePath` is rejected |
|
||||
| `FLOCI_SERVICES_ECS_ALLOW_UNSAFE_HOST_VOLUMES` | `false` | Allow any host path, bypassing `HOST_VOLUME_ROOTS`; traversal, the bare root, and the Docker socket (or an ancestor directory of it, e.g. `/var/run`) are still always rejected |
|
||||
|
||||
### EC2
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_EC2_ENABLED` | `true` | Enable the EC2 service |
|
||||
| `FLOCI_SERVICES_EC2_MOCK` | `false` | When `true`, instances are registered in state but no containers are spawned |
|
||||
| `FLOCI_SERVICES_EC2_IMDS_PORT` | `9169` | Port for the EC2 Instance Metadata Service (IMDS) endpoint |
|
||||
| `FLOCI_SERVICES_EC2_SSH_PORT_RANGE_START` | `2200` | First port in the SSH port range for EC2 instances |
|
||||
| `FLOCI_SERVICES_EC2_SSH_PORT_RANGE_END` | `2299` | Last port in the SSH port range |
|
||||
|
||||
### Athena
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_ATHENA_ENABLED` | `true` | Enable the Athena service |
|
||||
| `FLOCI_SERVICES_ATHENA_MOCK` | `false` | When `true`, queries are accepted but not executed |
|
||||
| `FLOCI_SERVICES_ATHENA_DEFAULT_IMAGE` | `floci/floci-duck:latest` | Docker image for the DuckDB query engine |
|
||||
| `FLOCI_SERVICES_ATHENA_DUCK_URL` | _(none)_ | URL of an existing DuckDB service. When set, Floci skips managing the container |
|
||||
|
||||
---
|
||||
|
||||
### Web Console (UI)
|
||||
|
||||
Floci starts the web console as a sidecar container the first time `/_floci/ui` is opened.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_UI_ENABLED` | `true` | Enable the web console sidecar |
|
||||
| `FLOCI_SERVICES_UI_IMAGE` | `floci/floci-ui:latest` | Console image to run |
|
||||
| `FLOCI_SERVICES_UI_CONTAINER_NAME` | `floci-ui` | Name of the sidecar container |
|
||||
| `FLOCI_SERVICES_UI_PORT` | `4500` | Host port the console is published on |
|
||||
| `FLOCI_SERVICES_UI_KEEP_RUNNING_ON_SHUTDOWN` | `false` | Leave the sidecar running when Floci stops |
|
||||
| `FLOCI_SERVICES_UI_DOCKER_NETWORK` | _(none)_ | Docker network for the sidecar (overrides `FLOCI_SERVICES_DOCKER_NETWORK`) |
|
||||
| `FLOCI_SERVICES_UI_ENDPOINT` | _(derived)_ | Floci endpoint handed to the console, instead of deriving it from the Docker host and TLS settings |
|
||||
| `FLOCI_SERVICES_UI_EXTRA_ENV` | _(none)_ | Extra `KEY=VALUE` entries for the console, comma-separated (escape a literal comma as `\,`). Applied last, so an entry may override an injected default |
|
||||
| `FLOCI_SERVICES_UI_INSECURE_SKIP_TLS_VERIFY` | `false` | Have the console skip TLS verification on its connection to Floci |
|
||||
| `FLOCI_SERVICES_UI_INTERNAL_PORT` | _(discovered, else `4500`)_ | Port the console listens on inside its container |
|
||||
| `FLOCI_SERVICES_UI_ENDPOINT_ENV` | _(none)_ | An extra variable to repeat the Floci endpoint in, for a console reading neither `AWS_ENDPOINT_URL` nor `FLOCI_ENDPOINT` |
|
||||
| `FLOCI_SERVICES_UI_STATUS_PATH` | _(discovered, else `/api/health`)_ | Path the readiness probe requests on the console |
|
||||
| `FLOCI_SERVICES_UI_STATUS_READY_FIELD` | _(discovered, else `status`)_ | JSON field in that response reporting readiness. Set to `none` to make any `200` count as ready |
|
||||
| `FLOCI_SERVICES_UI_STATUS_READY_VALUE` | _(discovered, else `ok`)_ | Value of that field meaning the console reached Floci |
|
||||
| `FLOCI_SERVICES_UI_STATUS_UNAVAILABLE_VALUE` | _(discovered, else `unavailable`)_ | Value meaning the console is up but cannot reach Floci |
|
||||
|
||||
The last six exist only for a console that neither follows the
|
||||
[console contract](../ui/console-contract.md) nor describes itself in its image labels. Leave them
|
||||
unset otherwise: setting one overrides both discovery paths.
|
||||
|
||||
See [Web Console](../ui/index.md) for running the console and swapping in a third-party one, and
|
||||
[Console Contract v1](../ui/console-contract.md) for writing one.
|
||||
|
||||
## Services — Additional
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_SERVICES_GLUE_ENABLED` | `true` | Enable the Glue service |
|
||||
| `FLOCI_SERVICES_APPSYNC_ENABLED` | `true` | Enable the AppSync service |
|
||||
| `FLOCI_SERVICES_BEDROCK_RUNTIME_ENABLED` | `true` | Enable the Bedrock Runtime service |
|
||||
| `FLOCI_SERVICES_BEDROCK_AGENT_CORE_CONTROL_ENABLED` | `true` | Enable the Bedrock AgentCore control plane (agent runtimes, gateways, memory, workload identity) |
|
||||
| `FLOCI_SERVICES_BEDROCK_AGENT_CORE_ENABLED` | `true` | Enable the Bedrock AgentCore data plane (`InvokeAgentRuntime` stub) |
|
||||
| `FLOCI_SERVICES_BEDROCK_AGENT_CORE_INVOKE_RESPONSE` | `{"output":"yes"}` | Canned body returned by `InvokeAgentRuntime` |
|
||||
| `FLOCI_SERVICES_BEDROCK_AGENT_CORE_VALIDATE_RUNTIME_EXISTS` | `false` | When `true`, `InvokeAgentRuntime` rejects unknown runtime ARNs |
|
||||
| `FLOCI_SERVICES_TEXTRACT_ENABLED` | `true` | Enable the Textract service |
|
||||
| `FLOCI_SERVICES_TRANSFER_ENABLED` | `true` | Enable the Transfer Family service |
|
||||
| `FLOCI_SERVICES_ROUTE53_ENABLED` | `true` | Enable the Route 53 service |
|
||||
| `FLOCI_SERVICES_ROUTE53_VPC_ASSOCIATION_CONTROL_PLANE_DELAY_MS` | `0` | Simulated processing window for Route 53 VPC association/auth mutations; positive values make documented overlap errors (`PriorRequestNotComplete` / `ConcurrentModification`) reproducible for retry tests |
|
||||
| `FLOCI_SERVICES_ELBV2_ENABLED` | `true` | Enable the ELBv2 (ALB/NLB) service |
|
||||
| `FLOCI_SERVICES_ELBV2_MOCK` | `false` | When `true`, load balancers are registered but no containers are spawned |
|
||||
| `FLOCI_SERVICES_AUTOSCALING_ENABLED` | `true` | Enable the Auto Scaling service |
|
||||
| `FLOCI_SERVICES_CODEBUILD_ENABLED` | `true` | Enable the CodeBuild service |
|
||||
| `FLOCI_SERVICES_CODEBUILD_DOCKER_NETWORK` | _(none)_ | Docker network for CodeBuild build containers |
|
||||
| `FLOCI_SERVICES_CODEDEPLOY_ENABLED` | `true` | Enable the CodeDeploy service |
|
||||
| `FLOCI_SERVICES_NETWORKFIREWALL_ENABLED` | `true` | Enable the AWS Network Firewall service |
|
||||
| `FLOCI_SERVICES_SERVICEQUOTAS_ENABLED` | `true` | Enable the Service Quotas service |
|
||||
| `FLOCI_SERVICES_RAM_ENABLED` | `true` | Enable the AWS RAM service |
|
||||
| `FLOCI_SERVICES_BACKUP_ENABLED` | `true` | Enable the AWS Backup service |
|
||||
| `FLOCI_SERVICES_BACKUP_JOB_COMPLETION_DELAY_SECONDS` | `3` | Simulated delay before backup jobs transition to `COMPLETED` |
|
||||
| `FLOCI_SERVICES_FIS_ENABLED` | `true` | Enable the AWS Fault Injection Service management API |
|
||||
| `FLOCI_SERVICES_RESOURCEEXPLORER2_ENABLED` | `true` | Enable the Resource Explorer 2 service |
|
||||
| `FLOCI_SERVICES_APPCONFIG_ENABLED` | `true` | Enable the AppConfig service |
|
||||
| `FLOCI_SERVICES_APPCONFIGDATA_ENABLED` | `true` | Enable the AppConfig Data service |
|
||||
@@ -0,0 +1,185 @@
|
||||
# Initialization Hooks
|
||||
|
||||
Floci supports init hook scripts that run at defined points in the startup and shutdown lifecycle.
|
||||
Use them to seed resources, configure state, or clean up after a run — before or after the AWS APIs are available.
|
||||
|
||||
!!! tip "Use the compat image for scripts that call `aws` or `boto3`"
|
||||
Scripts that invoke the AWS CLI or Python boto3 require the compat image, which bundles Python 3, the AWS CLI, and boto3 — all pre-configured for `http://localhost:4566`.
|
||||
Use `floci/floci:latest-compat` (or a pinned `x.y.z-compat`) instead of the standard image.
|
||||
|
||||
## Lifecycle Phases
|
||||
|
||||
Floci runs hooks in four ordered phases:
|
||||
|
||||
| Phase | When it runs | AWS APIs available? | Directory |
|
||||
|---|---|---|---|
|
||||
| **boot** | Before storage is loaded, before services start | No | `boot.d` |
|
||||
| **start** | After the HTTP server is ready on port 4566 | Yes ✅ | `start.d` |
|
||||
| **ready** | After all `start` hooks complete | Yes ✅ | `ready.d` |
|
||||
| **stop** | During pre-shutdown, while HTTP server is still up | Yes ✅ | `stop.d` |
|
||||
|
||||
The `/_floci/init` and `/_localstack/init` endpoints reflect each phase's completion status in real time, so external tooling can wait for `ready` before proceeding.
|
||||
|
||||
## Hook Directories
|
||||
|
||||
Floci merges scripts from two directory trees. The Floci-native tree has priority — if the same filename exists in both, the Floci copy runs and the LocalStack copy is skipped:
|
||||
|
||||
| Phase | Floci path | LocalStack-compat path |
|
||||
|---|---|---|
|
||||
| boot | `/etc/floci/init/boot.d` | `/etc/localstack/init/boot.d` |
|
||||
| start | `/etc/floci/init/start.d` | `/etc/localstack/init/start.d` |
|
||||
| ready | `/etc/floci/init/ready.d` | `/etc/localstack/init/ready.d` |
|
||||
| stop | `/etc/floci/init/stop.d` or `/etc/floci/init/shutdown.d` | `/etc/localstack/init/shutdown.d` |
|
||||
|
||||
The LocalStack-compat paths let existing LocalStack bootstrap scripts work without modification.
|
||||
Mount them under `/etc/localstack/init/` and they run as-is.
|
||||
|
||||
## Script Types
|
||||
|
||||
Floci discovers scripts with the following extensions:
|
||||
|
||||
- `.sh` — executed with the configured shell (default `/bin/sh`)
|
||||
- `.py` — executed with `python3`
|
||||
|
||||
Files with any other extension are ignored.
|
||||
|
||||
## Execution Order and Behavior
|
||||
|
||||
Within each phase, scripts run in **lexicographical order** and **sequentially** (one at a time).
|
||||
Prefix filenames with numbers to control order: `01-`, `02-`, `03-`, etc.
|
||||
|
||||
Floci uses a fail-fast strategy:
|
||||
|
||||
- If a script exits with a non-zero status, remaining scripts in that phase are skipped.
|
||||
- If a script exceeds the configured timeout, it is terminated and treated as a failure.
|
||||
- A `boot` or `start`/`ready` hook failure causes Floci to shut down.
|
||||
- A `stop` hook failure is logged but does not prevent shutdown or resource cleanup.
|
||||
|
||||
## AWS CLI in Hook Scripts
|
||||
|
||||
The compat image (`floci/floci:latest-compat`) includes the AWS CLI and boto3 with the local endpoint pre-configured.
|
||||
Scripts can call `aws` directly — no `--endpoint-url` flag needed:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
aws sqs create-queue --queue-name my-queue
|
||||
aws s3 mb s3://my-bucket
|
||||
aws ssm put-parameter --name /app/config --type String --value production
|
||||
```
|
||||
|
||||
The following environment variables are pre-set in the compat image:
|
||||
|
||||
| Variable | Value |
|
||||
|---|---|
|
||||
| `AWS_DEFAULT_REGION` | `us-east-1` |
|
||||
| `AWS_ACCESS_KEY_ID` | `test` |
|
||||
| `AWS_SECRET_ACCESS_KEY` | `test` |
|
||||
| `AWS_ENDPOINT_URL` | `http://localhost:4566` |
|
||||
| `AWS_CONFIG_FILE` | `/etc/floci/aws/config` |
|
||||
|
||||
Override any of them via `docker run -e` or the compose `environment` block.
|
||||
|
||||
Python scripts can use boto3 the same way — the config file is read automatically:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import boto3
|
||||
|
||||
sqs = boto3.client("sqs")
|
||||
sqs.create_queue(QueueName="my-queue")
|
||||
|
||||
s3 = boto3.client("s3")
|
||||
s3.create_bucket(Bucket="my-bucket")
|
||||
```
|
||||
|
||||
## Mounting Hook Directories
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest-compat
|
||||
ports:
|
||||
- "4566:4566"
|
||||
volumes:
|
||||
- ./init/boot.d:/etc/floci/init/boot.d:ro
|
||||
- ./init/start.d:/etc/floci/init/start.d:ro
|
||||
- ./init/ready.d:/etc/floci/init/ready.d:ro
|
||||
- ./init/stop.d:/etc/floci/init/stop.d:ro
|
||||
```
|
||||
|
||||
Phases you don't need can be omitted — Floci skips missing or empty directories.
|
||||
|
||||
### Migrating from LocalStack
|
||||
|
||||
If you have existing LocalStack init scripts, mount them under the LocalStack-compat paths and they work unchanged:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
volumes:
|
||||
- ./localstack-init/ready.d:/etc/localstack/init/ready.d:ro
|
||||
```
|
||||
|
||||
To override individual scripts with Floci-specific versions while keeping the rest:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
volumes:
|
||||
- ./localstack-init/ready.d:/etc/localstack/init/ready.d:ro # existing scripts
|
||||
- ./floci-init/ready.d:/etc/floci/init/ready.d:ro # overrides (take priority)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Seed resources on startup
|
||||
|
||||
```sh title="/etc/floci/init/ready.d/01-seed.sh"
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
aws sqs create-queue --queue-name orders
|
||||
aws s3 mb s3://assets
|
||||
aws ssm put-parameter --name /app/bootstrapped --type String --value true
|
||||
```
|
||||
|
||||
### Seed with Python + boto3
|
||||
|
||||
```python title="/etc/floci/init/ready.d/01-seed.py"
|
||||
#!/usr/bin/env python3
|
||||
import boto3
|
||||
|
||||
boto3.client("sqs").create_queue(QueueName="orders")
|
||||
boto3.client("s3").create_bucket(Bucket="assets")
|
||||
```
|
||||
|
||||
### Clean up on shutdown
|
||||
|
||||
```sh title="/etc/floci/init/stop.d/01-cleanup.sh"
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
aws ssm delete-parameter --name /app/bootstrapped
|
||||
```
|
||||
|
||||
!!! note "Shutdown timing"
|
||||
Stop hooks run before the HTTP server shuts down, so Floci's total shutdown time grows by
|
||||
the cumulative runtime of all stop hooks. Adjust your orchestrator grace period accordingly
|
||||
(e.g. Kubernetes `terminationGracePeriodSeconds`, Docker Compose `stop_grace_period`).
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_INIT_HOOKS_SHELL_EXECUTABLE` | `/bin/sh` | Shell used to run `.sh` scripts |
|
||||
| `FLOCI_INIT_HOOKS_TIMEOUT_SECONDS` | `30` | Maximum runtime per script before it is killed and treated as a failure |
|
||||
| `FLOCI_INIT_HOOKS_SHUTDOWN_GRACE_PERIOD_SECONDS` | `2` | Extra wait after terminating a timed-out script |
|
||||
|
||||
Example — extend the timeout for slow seed scripts:
|
||||
|
||||
```bash
|
||||
FLOCI_INIT_HOOKS_TIMEOUT_SECONDS=120
|
||||
```
|
||||
|
||||
Or in Docker Compose:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
FLOCI_INIT_HOOKS_TIMEOUT_SECONDS: "120"
|
||||
FLOCI_INIT_HOOKS_SHELL_EXECUTABLE: /bin/bash
|
||||
```
|
||||
@@ -0,0 +1,191 @@
|
||||
# Multi-Account Isolation
|
||||
|
||||
Floci supports full per-account resource isolation out of the box. Resources created by one account are invisible to all others — no configuration flag required.
|
||||
|
||||
## How It Works
|
||||
|
||||
Every incoming request carries an AWS SigV4 `Authorization` header. Floci reads the **Access Key ID** (AKID) from that header and resolves the account in this order:
|
||||
|
||||
1. An exactly 12-digit AKID is used directly as the account ID.
|
||||
2. An active long-term IAM access key resolves to the account that owns it.
|
||||
3. A live STS temporary credential resolves to its recorded account.
|
||||
4. Any other key falls back to `FLOCI_DEFAULT_ACCOUNT_ID`.
|
||||
|
||||
```
|
||||
Authorization: AWS4-HMAC-SHA256 Credential=111111111111/20260510/us-east-1/sqs/aws4_request, ...
|
||||
^^^^^^^^^^^^
|
||||
12-digit AKID → account ID 111111111111
|
||||
```
|
||||
|
||||
Once the account ID is determined, every storage read and write is transparently namespaced under it. An SQS queue named `orders` created by account `111111111111` is stored and retrieved as `111111111111/orders` — completely separate from the same queue name under account `222222222222`.
|
||||
|
||||
!!! note "Same convention as LocalStack"
|
||||
This 12-digit AKID → account ID rule matches LocalStack's multi-account behavior, so existing multi-account test setups work without changes.
|
||||
|
||||
## Long-Term IAM Credentials
|
||||
|
||||
Active access keys created through IAM route requests back to the account that owns the key. This lets a client keep using its generated `AKIA…` credentials instead of replacing the access key ID with a synthetic 12-digit account ID.
|
||||
|
||||
Account selection is AKID-based and is not proof that the caller possesses the corresponding secret. This extends Floci's existing local-emulator trust model for 12-digit AKIDs; do not treat account namespaces as a security boundary between mutually untrusted clients.
|
||||
|
||||
## Temporary Credentials (AssumeRole)
|
||||
|
||||
STS temporary credentials are also routed to the correct account. When you call `AssumeRole` (or `AssumeRoleWithWebIdentity`, `AssumeRoleWithSAML`, `GetFederationToken`), Floci issues a temporary `ASIA…` access key and remembers which account it belongs to:
|
||||
|
||||
- **`AssumeRole` / web-identity / SAML / federation** → the account in the role (or federated-user) ARN. Assuming `arn:aws:iam::222222222222:role/Deployer` from account `111111111111` yields credentials that resolve to **`222222222222`**, so resources created with them land in account B's namespace.
|
||||
- **`GetSessionToken`** → the caller's account (these credentials carry no role), so they stay in the same account.
|
||||
|
||||
```
|
||||
Account 111111111111 ──AssumeRole arn:aws:iam::222222222222:role/Deployer──▶ ASIA… temp key
|
||||
│
|
||||
ASIA… temp key ──CreateTable orders──▶ stored as 222222222222/...::orders ◀──┘
|
||||
```
|
||||
|
||||
This makes the cross-account `AssumeRole`-then-provision pattern (e.g. CloudFormation deploying into a target account) work locally exactly as it does in AWS.
|
||||
|
||||
## Default Behavior (Single Account)
|
||||
|
||||
If a credential is not a 12-digit account ID, an active IAM access key, or a live STS session (for example, `test`), requests resolve to the default account ID:
|
||||
|
||||
```bash
|
||||
FLOCI_DEFAULT_ACCOUNT_ID=000000000000 # default
|
||||
```
|
||||
|
||||
All ARNs and URLs use this value:
|
||||
|
||||
```
|
||||
arn:aws:sqs:us-east-1:000000000000:my-queue
|
||||
http://localhost:4566/000000000000/my-queue
|
||||
```
|
||||
|
||||
You can change the default account ID without enabling per-request isolation:
|
||||
|
||||
```bash
|
||||
FLOCI_DEFAULT_ACCOUNT_ID=123456789012
|
||||
```
|
||||
|
||||
## Enabling Multi-Account Isolation
|
||||
|
||||
Use 12-digit numeric access key IDs. The secret access key can be any non-empty string — Floci does not validate signatures by default.
|
||||
|
||||
### AWS CLI
|
||||
|
||||
```bash
|
||||
# Configure two named profiles
|
||||
aws configure --profile account-a
|
||||
# AWS Access Key ID: 111111111111
|
||||
# AWS Secret Access Key: test
|
||||
|
||||
aws configure --profile account-b
|
||||
# AWS Access Key ID: 222222222222
|
||||
# AWS Secret Access Key: test
|
||||
|
||||
export AWS_ENDPOINT_URL=http://localhost:4566
|
||||
|
||||
# Create the same queue name under both accounts
|
||||
aws sqs create-queue --queue-name orders --profile account-a
|
||||
aws sqs create-queue --queue-name orders --profile account-b
|
||||
|
||||
# Each account sees only its own queue
|
||||
aws sqs list-queues --profile account-a # → .../111111111111/orders
|
||||
aws sqs list-queues --profile account-b # → .../222222222222/orders
|
||||
```
|
||||
|
||||
### AWS SDK (Java)
|
||||
|
||||
```java
|
||||
StaticCredentialsProvider accountA = StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create("111111111111", "test"));
|
||||
|
||||
StaticCredentialsProvider accountB = StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create("222222222222", "test"));
|
||||
|
||||
SqsClient clientA = SqsClient.builder()
|
||||
.endpointOverride(URI.create("http://localhost:4566"))
|
||||
.region(Region.US_EAST_1)
|
||||
.credentialsProvider(accountA)
|
||||
.build();
|
||||
|
||||
SqsClient clientB = SqsClient.builder()
|
||||
.endpointOverride(URI.create("http://localhost:4566"))
|
||||
.region(Region.US_EAST_1)
|
||||
.credentialsProvider(accountB)
|
||||
.build();
|
||||
|
||||
// Both calls succeed, resources are fully isolated
|
||||
clientA.createQueue(r -> r.queueName("orders"));
|
||||
clientB.createQueue(r -> r.queueName("orders"));
|
||||
```
|
||||
|
||||
### AWS SDK (Python)
|
||||
|
||||
```python
|
||||
import boto3
|
||||
|
||||
def client(service, account_id):
|
||||
return boto3.client(
|
||||
service,
|
||||
endpoint_url="http://localhost:4566",
|
||||
region_name="us-east-1",
|
||||
aws_access_key_id=account_id, # 12-digit → account ID
|
||||
aws_secret_access_key="test",
|
||||
)
|
||||
|
||||
sqs_a = client("sqs", "111111111111")
|
||||
sqs_b = client("sqs", "222222222222")
|
||||
|
||||
sqs_a.create_queue(QueueName="orders")
|
||||
sqs_b.create_queue(QueueName="orders")
|
||||
|
||||
print(sqs_a.list_queues()["QueueUrls"]) # [".../111111111111/orders"]
|
||||
print(sqs_b.list_queues()["QueueUrls"]) # [".../222222222222/orders"]
|
||||
```
|
||||
|
||||
## ARNs Include the Correct Account ID
|
||||
|
||||
Floci embeds the resolved account ID in every ARN it generates:
|
||||
|
||||
```
|
||||
arn:aws:sqs:us-east-1:111111111111:orders
|
||||
arn:aws:lambda:us-east-1:222222222222:function:my-fn
|
||||
arn:aws:s3:::my-bucket # S3 ARNs are account-agnostic
|
||||
```
|
||||
|
||||
## Isolation Scope
|
||||
|
||||
All services that use `StorageFactory` participate in account isolation automatically. This covers every service in Floci — SQS, SNS, S3, DynamoDB, Lambda, SSM, Secrets Manager, KMS, Kinesis, EventBridge, Cognito, RDS, ElastiCache, OpenSearch, MSK, and more.
|
||||
|
||||
[AWS Organizations](../services/organizations.md) sits on top of this model: the organization's state lives in the management account's namespace, member accounts created with `CreateAccount` are immediately usable as 12-digit access key IDs, and — with IAM enforcement plus `FLOCI_SERVICES_ORGANIZATIONS_SCP_ENFORCEMENT_ENABLED` — service control policies attached in the organization constrain what member-account identities may do.
|
||||
|
||||
Background workers (Lambda event-source pollers, DynamoDB TTL sweeper, MSK readiness poller, OpenSearch readiness poller) iterate across all accounts internally and route writes back to the originating account. No cross-account data leaks through these async paths.
|
||||
|
||||
**S3 exception — global bucket namespace.** S3 buckets are isolated per account by default like every
|
||||
other service, but real S3 bucket names are globally unique. Set
|
||||
`FLOCI_SERVICES_S3_GLOBAL_BUCKET_NAMESPACE=true` to make bucket/object resolution span every account's
|
||||
partition, so a bucket created in one account is visible cross-account — needed when a workload writes a
|
||||
bucket in one account and reads it from another (see [S3](../services/s3.md#global-bucket-namespace)).
|
||||
|
||||
## Signature Validation
|
||||
|
||||
Floci does not currently perform general SigV4 validation for `Authorization` headers. Only the access key ID matters for account resolution, so the secret access key can be any non-empty string.
|
||||
|
||||
`FLOCI_AUTH_VALIDATE_SIGNATURES` currently applies only to S3 presigned URL validation. It does not authenticate general service requests or protect IAM and STS account routing. To validate S3 presigned URLs:
|
||||
|
||||
```bash
|
||||
FLOCI_AUTH_VALIDATE_SIGNATURES=true
|
||||
FLOCI_AUTH_PRESIGN_SECRET=your-secret # for pre-signed URL verification
|
||||
```
|
||||
|
||||
When `validate-signatures` is `false` (the default), S3 presigned URL signatures are not verified. Account routing remains AKID-based regardless of this setting.
|
||||
|
||||
## Persistence and Account Isolation
|
||||
|
||||
Storage keys are namespaced per account at the persistence layer. When using `persistent`, `hybrid`, or `wal` storage modes, each account's data is stored under its own key prefix. Restarting Floci restores each account's resources independently.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_DEFAULT_ACCOUNT_ID` | `000000000000` | Account ID used when the AKID does not resolve directly or through a stored credential |
|
||||
| `FLOCI_DEFAULT_REGION` | `us-east-1` | Region used when not derivable from the `Authorization` header |
|
||||
| `FLOCI_AUTH_VALIDATE_SIGNATURES` | `false` | Verify S3 presigned URL signatures |
|
||||
@@ -0,0 +1,190 @@
|
||||
# Ports Reference
|
||||
|
||||
## Port Overview
|
||||
|
||||
| Port / Range | Protocol | Purpose | docker-compose mapping required? |
|
||||
|---|---|---|---|
|
||||
| `4566` | HTTP | AWS APIs and ECR Docker Distribution | Yes |
|
||||
| `4500` | HTTP | Web console sidecar, bound directly by the console container | **No** (see note) |
|
||||
| `5100–5199` | HTTP | ECR Registry backing container, loopback-only implementation ports | No |
|
||||
| `5672–5699` | AMQP | Amazon MQ (RabbitMQ) AMQP listener, bound directly by each broker container | **No** |
|
||||
| `6379–6399` | TCP | ElastiCache Redis proxy (inside Floci) | Yes |
|
||||
| `6400–6419` | TCP | MemoryDB proxy (inside Floci) | Yes |
|
||||
| `6500–6599` | HTTPS | EKS k3s API server — bound directly by each k3s container | **No** |
|
||||
| `7001–7099` | TCP | RDS proxy (inside Floci) | Yes |
|
||||
| `8182–8282` | TCP | Neptune Gremlin proxy (inside Floci) | Yes |
|
||||
| `8700–8799` | HTTP | MWAA Airflow webserver proxy (inside Floci) | Yes |
|
||||
| `9400–9499` | HTTP | OpenSearch data-plane — bound directly by each OpenSearch container | **No** |
|
||||
| `12000–12499` | HTTP | Lambda Runtime API (internal, Docker-network only) | **No** |
|
||||
| `15672–15699` | HTTP | Amazon MQ (RabbitMQ) management console, bound directly by each broker container | **No** |
|
||||
|
||||
## Why some ports don't need docker-compose mapping
|
||||
|
||||
There are two distinct patterns Floci uses to expose container ports:
|
||||
|
||||
### Proxy-in-Floci (ElastiCache, MemoryDB, RDS, Neptune, MWAA)
|
||||
|
||||
Floci runs a **proxy process inside its own container**. The proxy listens on the host port and forwards traffic to the backend container.
|
||||
|
||||
```
|
||||
host:6379 → [docker-compose ports mapping] → Floci container:6379 → Redis container:6379
|
||||
```
|
||||
|
||||
Because the listener is inside the Floci container, `ports:` in `docker-compose.yml` is required to make it reachable from the host.
|
||||
|
||||
### Direct container binding (web console, EKS, OpenSearch, Amazon MQ)
|
||||
|
||||
Floci tells the Docker daemon to start a sidecar/service container and bind its port **directly on the host**. Floci itself communicates with the container via the shared Docker network (container name + internal port). The host port is bound by Docker, not by Floci.
|
||||
|
||||
```
|
||||
host:9400 ←── opensearch container:9200 (Docker binds 9400 directly on the host)
|
||||
↑
|
||||
Floci reaches it via Docker network: floci-opensearch-{name}:9200
|
||||
```
|
||||
|
||||
No `docker-compose.yml` `ports:` mapping is needed — the port is already on the host.
|
||||
|
||||
## Port 4566 — AWS API
|
||||
|
||||
Every AWS SDK and CLI call goes to port `4566`. This includes all management-plane operations: creating queues, putting items, invoking Lambdas, etc.
|
||||
|
||||
```bash
|
||||
aws s3 ls --endpoint-url http://localhost:4566
|
||||
aws sqs list-queues --endpoint-url http://localhost:4566
|
||||
aws lambda list-functions --endpoint-url http://localhost:4566
|
||||
```
|
||||
|
||||
## Ports 6379–6399 — ElastiCache
|
||||
|
||||
When you create an ElastiCache replication group, Floci starts a Valkey/Redis Docker container and creates a TCP proxy on the next available port in the `6379–6399` range. The proxy runs inside the Floci container, so this range must be mapped in `docker-compose.yml`.
|
||||
|
||||
```bash
|
||||
# Create a replication group
|
||||
aws elasticache create-replication-group \
|
||||
--replication-group-id my-redis \
|
||||
--replication-group-description "dev cache" \
|
||||
--endpoint-url http://localhost:4566
|
||||
|
||||
# Connect directly on the proxied port (returned in DescribeReplicationGroups Endpoint.Port)
|
||||
redis-cli -h localhost -p 6379
|
||||
```
|
||||
|
||||
!!! note
|
||||
Configure the range with `FLOCI_SERVICES_ELASTICACHE_PROXY_BASE_PORT` and `FLOCI_SERVICES_ELASTICACHE_PROXY_MAX_PORT`.
|
||||
|
||||
## Ports 6500–6599 — EKS (real mode)
|
||||
|
||||
When you create an EKS cluster in real mode, Floci asks the Docker daemon to start a k3s container and bind its API server port (6443) to the next available host port in `6500–6599`. The port is bound directly on the host by Docker — no `docker-compose.yml` mapping is needed.
|
||||
|
||||
The `endpoint` field returned by `DescribeCluster` points to `https://localhost:<hostPort>` when running outside a container, or `https://floci-eks-<name>:6443` when Floci is running inside Docker.
|
||||
|
||||
```bash
|
||||
aws eks create-cluster \
|
||||
--name my-cluster \
|
||||
--role-arn arn:aws:iam::000000000000:role/eks-role \
|
||||
--resources-vpc-config subnetIds=[],securityGroupIds=[] \
|
||||
--endpoint-url http://localhost:4566
|
||||
|
||||
# DescribeCluster returns the API server address, e.g. https://localhost:6500
|
||||
```
|
||||
|
||||
!!! note
|
||||
Configure the range with `FLOCI_SERVICES_EKS_API_SERVER_BASE_PORT` and `FLOCI_SERVICES_EKS_API_SERVER_MAX_PORT`.
|
||||
|
||||
## Ports 7001–7099 — RDS
|
||||
|
||||
When you create an RDS DB instance, Floci starts a PostgreSQL or MySQL Docker container and creates a TCP proxy on the next available port in the `7001–7099` range. The proxy runs inside the Floci container, so this range must be mapped in `docker-compose.yml`.
|
||||
|
||||
```bash
|
||||
aws rds create-db-instance \
|
||||
--db-instance-identifier mydb \
|
||||
--db-instance-class db.t3.micro \
|
||||
--engine postgres \
|
||||
--master-username admin \
|
||||
--master-user-password secret \
|
||||
--endpoint-url http://localhost:4566
|
||||
|
||||
# Connect using the proxied port (returned in DescribeDBInstances Endpoint.Port)
|
||||
psql -h localhost -p 7001 -U admin
|
||||
```
|
||||
|
||||
!!! note
|
||||
Configure the range with `FLOCI_SERVICES_RDS_PROXY_BASE_PORT` and `FLOCI_SERVICES_RDS_PROXY_MAX_PORT`.
|
||||
|
||||
## Ports 12000–12499 — Lambda Runtime API (internal)
|
||||
|
||||
Floci binds a Runtime API port in `12000–12499` for each warm Lambda container to poll. These ports are consumed by containers on the shared Docker network only — they are never accessed from the host and must **not** be mapped in `docker-compose.yml`.
|
||||
|
||||
One port is held for the lifetime of each running Lambda container, so the width of this range is a hard ceiling on **concurrent Lambda executions** — 500 with the default. This range was previously `9200–9299`; if you pinned that in your own configuration, raise it.
|
||||
|
||||
Configure the range with `FLOCI_SERVICES_LAMBDA_RUNTIME_API_BASE_PORT` and `FLOCI_SERVICES_LAMBDA_RUNTIME_API_MAX_PORT`.
|
||||
|
||||
## Ports 9400–9499 — OpenSearch (real mode)
|
||||
|
||||
When you create an OpenSearch domain in real mode, Floci asks the Docker daemon to start an `opensearchproject/opensearch` container and bind its REST port (9200) to the next available host port in `9400–9499`. The port is bound directly on the host by Docker — no `docker-compose.yml` mapping is needed.
|
||||
|
||||
The `endpoint` field returned by `DescribeDomain` points to `http://localhost:<hostPort>` when running outside a container, or `http://floci-opensearch-<name>:9200` when Floci is running inside Docker.
|
||||
|
||||
```bash
|
||||
aws opensearch create-domain \
|
||||
--domain-name my-search \
|
||||
--engine-version OpenSearch_2.11 \
|
||||
--endpoint-url http://localhost:4566
|
||||
|
||||
# DescribeDomain returns the data-plane address, e.g. http://localhost:9400
|
||||
curl http://localhost:9400/_cluster/health
|
||||
```
|
||||
|
||||
!!! note
|
||||
Configure the range with `FLOCI_SERVICES_OPENSEARCH_PROXY_BASE_PORT` and `FLOCI_SERVICES_OPENSEARCH_PROXY_MAX_PORT`.
|
||||
|
||||
## Ports 5672–5699 and 15672–15699: Amazon MQ (RabbitMQ, real mode)
|
||||
|
||||
When you create an Amazon MQ broker in real mode, Floci starts a `rabbitmq:3-management` container and binds its AMQP port (5672) to the next available host port in `5672–5699`, and its management console (15672) to the next available host port in `15672–15699`. Both are bound directly on the host by Docker, in every topology. No `docker-compose.yml` mapping is needed, and adding one on the `floci` service would only conflict with the broker container's binding.
|
||||
|
||||
The `BrokerInstances[].Endpoints` returned by `DescribeBroker` point to `amqp://localhost:<hostPort>` when Floci runs outside a container, or to the broker container's Docker-network IP when Floci runs inside Docker. In the second case, host clients should use the published host port instead (`docker ps` shows it); the first broker normally lands on `5672` / `15672`.
|
||||
|
||||
```bash
|
||||
aws mq create-broker --broker-name my-broker --engine-type RABBITMQ \
|
||||
--engine-version "3.13" --deployment-mode SINGLE_INSTANCE \
|
||||
--host-instance-type mq.t3.micro --no-publicly-accessible --auto-minor-version-upgrade \
|
||||
--users '[{"Username":"admin","Password":"AdminPass123","ConsoleAccess":true}]' \
|
||||
--endpoint-url http://localhost:4566
|
||||
|
||||
# From the host, once the broker is RUNNING:
|
||||
# amqp://admin:AdminPass123@localhost:5672/
|
||||
# http://localhost:15672 (management console)
|
||||
```
|
||||
|
||||
!!! note
|
||||
Configure the ranges with `FLOCI_SERVICES_AMAZONMQ_AMQP_HOST_PORT_BASE` / `_MAX` and `FLOCI_SERVICES_AMAZONMQ_CONSOLE_HOST_PORT_BASE` / `_MAX`.
|
||||
|
||||
## Ports 5100–5199: ECR Registry backing ports
|
||||
|
||||
ECR uses the Floci `4566` listener for Docker Distribution and AWS API traffic. The `registry:2`
|
||||
backing container binds a loopback-only implementation port in `5100-5199`; clients must use the
|
||||
repository URI returned by `CreateRepository`, for example
|
||||
`000000000000.dkr.ecr.us-east-1.localhost:4566/my-repo`.
|
||||
|
||||
## Exposing Ports in Docker Compose
|
||||
|
||||
Only the proxy-based services (ElastiCache, MemoryDB, RDS, Neptune, MWAA) need port mappings in `docker-compose.yml`. Direct-binding services (EKS, OpenSearch, Amazon MQ) bind their ports on the host automatically via Docker:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
ports:
|
||||
- "4566:4566" # All AWS API calls
|
||||
- "6379-6399:6379-6399" # ElastiCache / Redis proxy (proxy in Floci)
|
||||
- "6400-6419:6400-6419" # MemoryDB proxy (proxy in Floci)
|
||||
- "7001-7099:7001-7099" # RDS proxy (proxy in Floci)
|
||||
- "8182-8282:8182-8282" # Neptune Gremlin proxy (proxy in Floci)
|
||||
- "8700-8799:8700-8799" # MWAA webserver proxy (proxy in Floci)
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
EKS (6500–6599), OpenSearch (9400–9499) and Amazon MQ (5672–5699, 15672–15699) ports are bound directly on the host by Docker and are accessible without any `ports:` entry. ECR uses the existing `4566` mapping.
|
||||
|
||||
If your application runs inside the same Docker Compose network, it can reach Floci directly on container port `4566` — the host port mapping is only needed for tools running on the host (CLI, IDE plugins, etc.).
|
||||
@@ -0,0 +1,176 @@
|
||||
# Storage Modes
|
||||
|
||||
Floci supports four storage backends. You can set a global default and override it per service.
|
||||
|
||||
## Modes
|
||||
|
||||
| Mode | Data survives restart | Write performance | Use case |
|
||||
|---|---|---|---|
|
||||
| `memory` | No | Fastest | Unit tests, CI pipelines |
|
||||
| `persistent` | Yes | Synchronous disk write on every change | Development with durable state |
|
||||
| `hybrid` | Yes | In-memory reads, async flush to disk | General local development |
|
||||
| `wal` | Yes | Append-only write-ahead log with compaction | High-write workloads |
|
||||
|
||||
## Global Configuration
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_STORAGE_MODE` | `memory` | Storage backend (`memory`, `persistent`, `hybrid`, `wal`) |
|
||||
| `FLOCI_STORAGE_PERSISTENT_PATH` | `./data` | Base directory for all persistent data |
|
||||
| `FLOCI_STORAGE_WAL_COMPACTION_INTERVAL_MS` | `30000` | WAL compaction interval (milliseconds) |
|
||||
|
||||
!!! note "Code default vs shipped default"
|
||||
The Java `@WithDefault` for `storage.mode` is `hybrid`, but the published Docker image ships with `memory` set in `application.yml`. Running the stock image gives you `memory` unless you set `FLOCI_STORAGE_MODE`.
|
||||
|
||||
## Per-Service Override
|
||||
|
||||
When not set for a service, it inherits `FLOCI_STORAGE_MODE`. Only override when you need different behavior for a specific service.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `FLOCI_STORAGE_SERVICES_SSM_MODE` | global default | SSM storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_SSM_FLUSH_INTERVAL_MS` | `5000` | SSM flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_SQS_MODE` | global default | SQS storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_S3_MODE` | global default | S3 storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_DYNAMODB_MODE` | global default | DynamoDB storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_DYNAMODB_FLUSH_INTERVAL_MS` | `5000` | DynamoDB flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_SNS_MODE` | global default | SNS storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_SNS_FLUSH_INTERVAL_MS` | `5000` | SNS flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_LAMBDA_MODE` | global default | Lambda storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_LAMBDA_FLUSH_INTERVAL_MS` | `5000` | Lambda flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_CLOUDWATCHLOGS_MODE` | global default | CloudWatch Logs storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_CLOUDWATCHLOGS_FLUSH_INTERVAL_MS` | `5000` | CloudWatch Logs flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_CLOUDWATCHMETRICS_MODE` | global default | CloudWatch Metrics storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_CLOUDWATCHMETRICS_FLUSH_INTERVAL_MS` | `5000` | CloudWatch Metrics flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_SECRETSMANAGER_MODE` | global default | Secrets Manager storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_SECRETSMANAGER_FLUSH_INTERVAL_MS` | `5000` | Secrets Manager flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_ACM_MODE` | global default | ACM storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_ACM_FLUSH_INTERVAL_MS` | `5000` | ACM flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_APPSYNC_MODE` | global default | AppSync storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_APPSYNC_FLUSH_INTERVAL_MS` | `5000` | AppSync flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_OPENSEARCH_MODE` | global default | OpenSearch storage mode |
|
||||
| `FLOCI_STORAGE_SERVICES_OPENSEARCH_FLUSH_INTERVAL_MS` | `5000` | OpenSearch flush interval (ms) |
|
||||
| `FLOCI_STORAGE_SERVICES_RDS_MODE` | global default | RDS metadata storage mode (see note below) |
|
||||
|
||||
!!! note "RDS storage mode"
|
||||
`FLOCI_STORAGE_SERVICES_RDS_MODE` controls Floci's own metadata persistence for RDS, not the
|
||||
DB container volumes. In all modes, each DB instance or cluster gets a named Docker volume
|
||||
(`floci-rds-{volumeId}`). In `memory` mode the volume is automatically removed when the
|
||||
instance is deleted. In other modes the volume is retained unless
|
||||
`FLOCI_STORAGE_PRUNE_VOLUMES_ON_DELETE=true`.
|
||||
|
||||
## Recommended Profiles
|
||||
|
||||
=== "Fast CI"
|
||||
|
||||
All in memory — fastest possible startup and test execution:
|
||||
|
||||
```bash
|
||||
FLOCI_STORAGE_MODE=memory
|
||||
```
|
||||
|
||||
=== "Local development"
|
||||
|
||||
Hybrid — survive restarts without slowing down writes:
|
||||
|
||||
```bash
|
||||
FLOCI_STORAGE_MODE=hybrid
|
||||
FLOCI_STORAGE_PERSISTENT_PATH=/app/data
|
||||
```
|
||||
|
||||
Docker Compose:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- floci-data:/app/data
|
||||
environment:
|
||||
FLOCI_STORAGE_MODE: hybrid
|
||||
FLOCI_STORAGE_PERSISTENT_PATH: /app/data
|
||||
```
|
||||
|
||||
=== "Durable development"
|
||||
|
||||
Persistent — every write is immediately on disk:
|
||||
|
||||
```bash
|
||||
FLOCI_STORAGE_MODE=persistent
|
||||
FLOCI_STORAGE_PERSISTENT_PATH=/app/data
|
||||
```
|
||||
|
||||
Docker Compose:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- floci-data:/app/data
|
||||
environment:
|
||||
FLOCI_STORAGE_MODE: persistent
|
||||
FLOCI_STORAGE_PERSISTENT_PATH: /app/data
|
||||
```
|
||||
|
||||
=== "Mixed"
|
||||
|
||||
Keep most services in memory, persist only DynamoDB and S3:
|
||||
|
||||
```bash
|
||||
FLOCI_STORAGE_MODE=memory
|
||||
FLOCI_STORAGE_SERVICES_DYNAMODB_MODE=persistent
|
||||
FLOCI_STORAGE_SERVICES_S3_MODE=hybrid
|
||||
FLOCI_STORAGE_PERSISTENT_PATH=/app/data
|
||||
```
|
||||
|
||||
## Container Storage (RDS, OpenSearch, MSK, ECR)
|
||||
|
||||
Services that spawn Docker containers (RDS, OpenSearch, MSK, ECR registry) need a volume for their
|
||||
data. Floci manages this automatically using **named Docker volumes** — no extra configuration
|
||||
required.
|
||||
|
||||
### How it works
|
||||
|
||||
Each resource gets a `volumeId` (a 6-character hex string, e.g. `a1b2c3`) generated at creation
|
||||
time and stored in the resource model. The container name and volume name both use this suffix:
|
||||
|
||||
```
|
||||
floci-rds-a1b2c3 # RDS instance container and volume
|
||||
floci-opensearch-b4c5d6 # OpenSearch domain container and volume
|
||||
floci-msk-e7f8a9 # MSK cluster container and volume
|
||||
floci-ecr-registry-data # ECR shared registry volume (singleton)
|
||||
```
|
||||
|
||||
Volumes are labelled `floci=true` so you can manage them with standard Docker commands:
|
||||
|
||||
```bash
|
||||
# List all Floci-managed volumes
|
||||
docker volume ls --filter label=floci=true
|
||||
|
||||
# Remove all Floci-managed volumes (destructive)
|
||||
docker volume prune --filter label=floci=true
|
||||
```
|
||||
|
||||
### Volume lifecycle
|
||||
|
||||
By default volumes survive resource deletion, matching real AWS behavior.
|
||||
|
||||
| `FLOCI_STORAGE_MODE` | Volume on resource delete |
|
||||
|---|---|
|
||||
| `memory` | **Always removed** — memory mode implies no persistence across restarts |
|
||||
| `persistent` / `hybrid` / `wal` | Retained (default) — remove with `FLOCI_STORAGE_PRUNE_VOLUMES_ON_DELETE=true` |
|
||||
|
||||
```bash
|
||||
# Remove named volumes immediately when a resource is deleted (useful in CI with persistent mode)
|
||||
FLOCI_STORAGE_PRUNE_VOLUMES_ON_DELETE=true
|
||||
```
|
||||
|
||||
### Host-path mode (advanced)
|
||||
|
||||
Set `FLOCI_STORAGE_HOST_PERSISTENT_PATH` to an **absolute host path** to use bind mounts instead
|
||||
of named volumes. This is only needed when you must access the container data directly from the
|
||||
host filesystem.
|
||||
|
||||
```bash
|
||||
FLOCI_STORAGE_HOST_PERSISTENT_PATH=/absolute/host/path/data
|
||||
```
|
||||
|
||||
!!! warning
|
||||
`FLOCI_STORAGE_HOST_PERSISTENT_PATH` must be an absolute path (starting with `/`). Volume
|
||||
names and relative paths are not supported and will be silently ignored, falling back to
|
||||
named-volume mode.
|
||||
@@ -0,0 +1,232 @@
|
||||
# TLS / HTTPS
|
||||
|
||||
Floci supports optional TLS, enabling `https://` for all REST/JSON/Query endpoints and `wss://` for WebSocket connections. Both HTTP and HTTPS are served simultaneously (LocalStack parity).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
docker run -e FLOCI_TLS_ENABLED=true -p 4566:4566 floci/floci:latest
|
||||
```
|
||||
|
||||
Then point your SDK at `https://localhost:4566` and trust Floci's local CA in the processes that talk to it. Every certificate Floci issues (its HTTPS endpoint, every ACM certificate and every IoT device certificate) chains to that one CA:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:4566/_floci/ca.pem -o floci-root-ca.pem
|
||||
|
||||
# AWS CLI and every AWS SDK
|
||||
export AWS_CA_BUNDLE=$PWD/floci-root-ca.pem
|
||||
aws --endpoint-url https://localhost:4566 sts get-caller-identity
|
||||
|
||||
# Node.js (appends to the built-in roots)
|
||||
export NODE_EXTRA_CA_CERTS=$PWD/floci-root-ca.pem
|
||||
|
||||
# curl, Python, Go on Linux (replaces the default roots for that process only)
|
||||
export SSL_CERT_FILE=$PWD/floci-root-ca.pem
|
||||
```
|
||||
|
||||
Prefer these per-process variables, and scope them to the shell or the tool (a `.envrc`, a compose `environment:` block) that needs them. They cannot affect anything else on the machine.
|
||||
|
||||
The download above is plain HTTP and carries no proof of who sent it, which is fine on loopback and nowhere else. For a client on another host, or over a network you do not control, copy the file out of band instead: read `{persistent-path}/tls/floci-root-ca.crt` from Floci's volume, or `docker cp` it out of the container. Either way, compare the fingerprint before trusting it. Floci logs it at startup as `TLS: ... SHA256 fingerprint AB:CD:...`, and the file's fingerprint is:
|
||||
|
||||
```bash
|
||||
openssl x509 -in floci-root-ca.pem -noout -fingerprint -sha256
|
||||
```
|
||||
|
||||
Installing the CA into the operating system trust store (macOS `security add-trusted-cert`, Linux `update-ca-certificates`) also works and is what Safari, Chrome and Go on macOS need, but understand what it does: every process on the machine then trusts anything signed by the key in `{persistent-path}/tls/floci-root-ca.key`. Keep that file private, treat the CA as a dev-machine secret, and remove it from the store when you stop using Floci:
|
||||
|
||||
```bash
|
||||
# macOS login keychain; remove later with: security delete-certificate -c "Floci Local CA"
|
||||
security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain-db floci-root-ca.pem
|
||||
```
|
||||
|
||||
Disabling verification (`--no-verify-ssl`, `verify=False`, `NODE_TLS_REJECT_UNAUTHORIZED=0`) still works but is no longer needed.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Environment Variable | Default | Description |
|
||||
|---------------------|---------|-------------|
|
||||
| `FLOCI_TLS_ENABLED` | `false` | Enable TLS/HTTPS on the server |
|
||||
| `FLOCI_TLS_CERT_PATH` | *(unset)* | Path to PEM certificate file |
|
||||
| `FLOCI_TLS_KEY_PATH` | *(unset)* | Path to PEM private key file |
|
||||
| `FLOCI_TLS_SELF_SIGNED` | `true` | Auto-generate a server certificate signed by Floci's local CA when no cert/key paths provided |
|
||||
|
||||
With TLS enabled, the proxy that accepts HTTP and HTTPS on Floci's ports listens on `QUARKUS_HTTP_HOST` (`127.0.0.1` by default). As without TLS, any address outside loopback also needs `FLOCI_SECURITY_ALLOW_UNSAFE_NETWORK_EXPOSURE=true`; see [Network Exposure](./environment-variables.md#network-exposure).
|
||||
|
||||
## Local CA and Server Certificate
|
||||
|
||||
When `FLOCI_TLS_ENABLED=true` and no custom certificate is provided, Floci keeps a local root CA at `{persistent-path}/tls/floci-root-ca.crt` (key `floci-root-ca.key`, owner-only) and issues its server certificate `floci-server.crt` from it at startup. The server certificate:
|
||||
|
||||
- Is persisted to `{persistent-path}/tls/` and reused across restarts
|
||||
- Includes `localhost`, `127.0.0.1`, `0.0.0.0`, `*.localhost`, `localhost.floci.io`,
|
||||
`*.localhost.floci.io`, `*.execute-api.localhost.floci.io`,
|
||||
`*.execute-api.localhost.localstack.cloud`, `*.cloudfront.localhost.floci.io`,
|
||||
`*.cloudfront.localhost`, and `*.dkr.ecr.<region>.localhost.floci.io` for every advertised
|
||||
AWS region as Subject Alternative Names (SANs)
|
||||
- Automatically includes custom hostnames from `FLOCI_HOSTNAME`, `FLOCI_BASE_URL` and `FLOCI_SERVICES_IOT_ENDPOINT_ADDRESS` in the SANs
|
||||
- Is regenerated when hostname configuration changes between restarts, or when it was not issued by the current CA
|
||||
|
||||
The CA is created once and never rotates on its own. If its files are missing, corrupt or do not match each other, Floci generates a new CA, logs a warning, and reissues the server certificate; clients then need the new `ca.pem`.
|
||||
|
||||
### Custom Hostname Support
|
||||
|
||||
If you set `FLOCI_HOSTNAME` or use a custom host in `FLOCI_BASE_URL`, the server certificate automatically includes those hostnames in its SANs. This is essential for Docker Compose setups where containers reference Floci by service name:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
floci:
|
||||
image: floci/floci:latest
|
||||
environment:
|
||||
FLOCI_TLS_ENABLED: "true"
|
||||
FLOCI_HOSTNAME: floci
|
||||
ports:
|
||||
- "4566:4566"
|
||||
|
||||
app:
|
||||
environment:
|
||||
AWS_ENDPOINT_URL: "https://floci:4566"
|
||||
AWS_CA_BUNDLE: /floci/floci-root-ca.pem
|
||||
```
|
||||
|
||||
The generated certificate will include `floci` in its SANs, so TLS validation succeeds when `app` connects to `https://floci:4566`. Fetch `ca.pem` from Floci into `app` (an entrypoint `curl`, or a shared volume mounted from `{persistent-path}/tls/`) so the bundle path above exists.
|
||||
|
||||
### Custom Domains Learned at Runtime
|
||||
|
||||
A wildcard SAN matches one label only, so `*.localhost.floci.io` covers `api.localhost.floci.io` but not `api.dev.localhost.floci.io`. For names like that, Floci can add an exact hostname to the server certificate while it runs: the certificate is reissued by the local CA with the same key and the new name, and the HTTPS listener switches to it at once. No restart, no new CA, nothing new for clients to trust. Learned names are recorded in `{persistent-path}/tls/floci-server.metadata.json`, so they survive restarts and a regeneration after a `FLOCI_HOSTNAME` change; `POST /_floci/state/reset` drops them.
|
||||
|
||||
Only a name under a local suffix is accepted: `localhost`, `localhost.floci.io`, `localhost.localstack.cloud`, `FLOCI_HOSTNAME`, the `FLOCI_BASE_URL` host, and every `FLOCI_DNS_EXTRA_SUFFIXES` entry. Any other name is refused with a warning, so a Floci certificate can never cover a public name. This only applies to the generated certificate; a user-provided one is never changed.
|
||||
|
||||
Names are added by the custom domain operations: API Gateway `CreateDomainName`, IoT Core `CreateDomainConfiguration` with a `domainName`, and Cognito `CreateUserPoolDomain` with a `CustomDomainConfig`. The AWS operation succeeds either way; a name outside the local suffixes, or a reissue that fails, is logged as a warning.
|
||||
|
||||
### Containers Floci Launches
|
||||
|
||||
Every container Floci launches, from Lambda functions and ECS or Batch tasks to the Docker backends behind RDS, ElastiCache, MWAA and Flink, and the Kubernetes Lambda pods, receives `/etc/floci-ca-bundle.pem`: the public root CAs the Floci JVM trusts, followed by the Floci CA (or your certificate file when you provide one). `SSL_CERT_FILE`, `CURL_CA_BUNDLE`, `REQUESTS_CA_BUNDLE`, `NODE_EXTRA_CA_CERTS` and `AWS_CA_BUNDLE` point at it, so curl, Python, Node.js, Go, the AWS CLI and the AWS SDKs that read `AWS_CA_BUNDLE` reach both Floci and public HTTPS sites with no setup in the workload. A variable you set on the function or task definition wins over the injected one. The JVM ignores all of these variables, so a Java workload still needs its own trust store, as shown under SDK Configuration Examples. The bundle is written to `{persistent-path}/tls/floci-ca-bundle.pem` at startup; when it is missing, or when an image has no `/etc` to copy it into, the container starts without the variables and Floci logs a warning.
|
||||
|
||||
## User-Provided Certificates
|
||||
|
||||
To use your own certificate (e.g., from a corporate CA or mkcert):
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-e FLOCI_TLS_ENABLED=true \
|
||||
-e FLOCI_TLS_CERT_PATH=/certs/server.crt \
|
||||
-e FLOCI_TLS_KEY_PATH=/certs/server.key \
|
||||
-v ./certs:/certs:ro \
|
||||
-p 4566:4566 \
|
||||
floci/floci:latest
|
||||
```
|
||||
|
||||
When custom certificate paths are provided, `FLOCI_TLS_SELF_SIGNED` is ignored and the HTTPS server uses your certificate; no server certificate is generated. `GET /_floci/ca.pem` then returns a PEM bundle: the certificates in your certificate file first (a private key kept in that file is never served), then Floci's local CA, which is created on first use and signs the certificates Floci itself issues (IoT device certificates). Put the CA or the full chain in your certificate file if clients fetch their trust anchor from Floci; a bare leaf only lets them pin that one certificate.
|
||||
|
||||
## WebSocket (wss://)
|
||||
|
||||
When TLS is enabled, WebSocket connections automatically use `wss://`:
|
||||
|
||||
```
|
||||
wss://localhost:4566/ws/{apiId}/{stage}
|
||||
```
|
||||
|
||||
No additional configuration is needed: Vert.x handles TLS at the transport layer transparently.
|
||||
|
||||
## MQTT over TLS (8883)
|
||||
|
||||
When TLS is enabled, the IoT MQTT broker also listens on port 8883 with the same certificate as the HTTPS endpoint, hostnames learned at runtime included. Devices connect with `ssl://` trusting `ca.pem`; see [IoT Core](../services/iot.md#mqtt-over-tls). Devices that take the hostname from `DescribeEndpoint` and add their own port need `FLOCI_SERVICES_IOT_ENDPOINT_ADDRESS`, see [Endpoint address](../services/iot.md#endpoint-address).
|
||||
|
||||
## SDK Configuration Examples
|
||||
|
||||
With `AWS_CA_BUNDLE` set as in Quick Start, no SDK needs code changes. The examples below trust the CA in code instead, for processes whose environment you do not control.
|
||||
|
||||
### AWS SDK for JavaScript v3
|
||||
|
||||
```typescript
|
||||
import { STSClient } from '@aws-sdk/client-sts';
|
||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||
import https from 'node:https';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const client = new STSClient({
|
||||
endpoint: 'https://localhost:4566',
|
||||
region: 'us-east-1',
|
||||
credentials: { accessKeyId: 'test', secretAccessKey: 'test' },
|
||||
requestHandler: new NodeHttpHandler({
|
||||
httpsAgent: new https.Agent({ ca: readFileSync('floci-root-ca.pem') }),
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Or set the environment variable globally:
|
||||
|
||||
```bash
|
||||
NODE_EXTRA_CA_CERTS=$PWD/floci-root-ca.pem npx vitest run
|
||||
```
|
||||
|
||||
### AWS SDK for Java v2
|
||||
|
||||
```java
|
||||
SdkHttpClient httpClient = ApacheHttpClient.builder()
|
||||
.tlsTrustManagersProvider(() -> {
|
||||
try {
|
||||
var ca = CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(Files.newInputStream(Path.of("floci-root-ca.pem")));
|
||||
var trust = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
trust.load(null, null);
|
||||
trust.setCertificateEntry("floci", ca);
|
||||
var factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
factory.init(trust);
|
||||
return factory.getTrustManagers();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
StsClient sts = StsClient.builder()
|
||||
.endpointOverride(URI.create("https://localhost:4566"))
|
||||
.httpClient(httpClient)
|
||||
.build();
|
||||
```
|
||||
|
||||
### Python (boto3)
|
||||
|
||||
```python
|
||||
import boto3
|
||||
|
||||
client = boto3.client(
|
||||
'sts',
|
||||
endpoint_url='https://localhost:4566',
|
||||
verify='floci-root-ca.pem', # or export AWS_CA_BUNDLE
|
||||
region_name='us-east-1',
|
||||
aws_access_key_id='test',
|
||||
aws_secret_access_key='test',
|
||||
)
|
||||
```
|
||||
|
||||
## When Do I Need to Disable TLS Verification?
|
||||
|
||||
| Certificate type | Verification disabled? | Why |
|
||||
|-----------------|----------------------|-----|
|
||||
| Floci local CA (default) | **No**, once you trust `floci-root-ca.crt` (`GET /_floci/ca.pem`) | One CA covers the HTTPS endpoint and every certificate Floci issues |
|
||||
| `mkcert` with local CA installed | **No** | `mkcert -install` adds its root CA to the OS trust store |
|
||||
| Corporate/internal CA already trusted | **No** | Your OS or JVM already trusts the issuing CA |
|
||||
| Public CA (Let's Encrypt, etc.) | **No** | Trusted by default in all runtimes |
|
||||
|
||||
In short: you only need to disable verification when the certificate's issuer is **not** in the client's trust chain. If you provide your own certificate via `FLOCI_TLS_CERT_PATH` and its CA is already trusted by your system, no extra client configuration is needed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Certificate errors after changing `FLOCI_HOSTNAME`.**
|
||||
Floci detects hostname configuration changes and regenerates the server certificate automatically. If you still see errors, delete the `{persistent-path}/tls/` directory and restart. That also creates a new CA, so re-download `ca.pem`.
|
||||
|
||||
**`UNABLE_TO_GET_ISSUER_CERT_LOCALLY` or `DEPTH_ZERO_SELF_SIGNED_CERT` in Node.js.**
|
||||
The process does not trust the Floci CA yet. Set `NODE_EXTRA_CA_CERTS` to `ca.pem` or pass it as the agent's `ca` option as shown above.
|
||||
|
||||
**Java `SSLHandshakeException: PKIX path building failed`.**
|
||||
Import `ca.pem` into the trust store the client uses (`keytool -importcert -file floci-root-ca.pem -alias floci -cacerts`, or a `TrustManager` built from it as shown above). For user-provided certificates from a trusted CA, this should not occur.
|
||||
|
||||
**`Go: x509: certificate signed by unknown authority` on macOS.**
|
||||
Go on macOS ignores `SSL_CERT_FILE` and reads the keychain. Pin the CA in code with `x509.NewCertPool().AppendCertsFromPEM` (the AWS SDK for Go honours `AWS_CA_BUNDLE` regardless), or add it to the login keychain as shown in Quick Start.
|
||||
|
||||
**I regenerated `{persistent-path}/tls/`.**
|
||||
That created a new CA. Re-download `/_floci/ca.pem` wherever you installed the old one.
|
||||
|
||||
**Certificate doesn't include my custom hostname.**
|
||||
Ensure `FLOCI_HOSTNAME` or `FLOCI_BASE_URL` is set *before* Floci starts. The certificate is generated during startup. Check the logs for `TLS: detected custom hostnames: [...]`.
|
||||
Reference in New Issue
Block a user