Introduction
As of March 2026, LocalStack now requires an auth token to start the AWS environment. That change was enough to push this setup toward Floci, an open-source alternative that can emulate AWS and other cloud providers such as Azure and GCP.
In this post, we will walk through the migration process from the original Localstack setup to the new Floci architecture for deploying our static Hugo site.
Architecture
The architecture is split into two containerized services, Floci and Hugo.
Floci acts as the local AWS-compatible backend. It exposes the S3-compatible endpoint, hosts the bucket, and receives all storage operations that the site needs during deployment.
The Hugo container is the build and deployment worker. It builds and renders the static site, runs the AWS CLI commands, and pushes the generated files into Floci’s S3 bucket.
Docker Compose ties the two services together and orchestrates their operation.
Docker Compose
The compose file below shows the overall layout:
| |
There are two important configurations for Floci:
- Port
4566, which is exposed on the host for local access. - Hostname, so that other locally running containers can reach Floci. This is set through the environment variable
FLOCI_HOSTNAME.
For our site, habibiops, we only need to define AWS_ENDPOINT_URL, which points to the Floci hostname and port.
AWS_ENDPOINT_URL only works between containers. If you need to access Floci from your host machine, use
localhost:4566 or 0.0.0.0:4566 instead, without changing /etc/hosts.
Hugo Container
The Hugo image changed in two important ways:
localstackwas removed from the base image and replaced withubuntuaws-cliwas added so the build can still publish content to S3
The Dockerfile now looks like this:
| |
One important note: the AWS CLI package is architecture dependent. The download URL changes depending on whether the
host is running on x86_64 or aarch64. The arch command can be used to detect the current architecture before
choosing the correct installer URL.
Hugo Setup Script
The setup script keeps the same deployment steps, but adds HUGO_ENV so the build can distinguish between local and
production runs.
Hugo differentiates between different environments based on the folder name in the config directory. For example, if
we want to define dev and prod, then we would have this structure:
| |
The _default applies to all environments. Any changes or differences between environments would be implemented and
defined separately within their respective folders. For example, dev/config.toml is edited to include these variables:
| |
While prod/config.toml contains:
| |
Below is the minimal setup.sh script, including the environment variable:
| |
Deploying Changes
When the site changes, the habibiops container needs to be rebuilt. Docker Compose will otherwise reuse the cached
image, so the update needs to be explicit:
| |
In this setup, depends_on controls the flow of execution. Compose starts floci first, then brings up
habibiops. After floci is running, habibiops starts building, creates the S3 bucket, builds the Hugo site, and
deploys it to S3 via the Floci endpoint. Once that finishes, habibiops container exits (with status 0) while floci
remains running and serves the deployed static site.
Finally, we can access the site locally through the same endpoint used by the containerized S3 workflow:
| |
Conclusion
The migration adds some complexity, mainly because Docker Compose is now part of the deployment flow and the setup script had to be adjusted for the new endpoint model. The tradeoff is cleaner separation: Floci runs as an independent service, and any AWS-backed workload can sit beside it as its own container while still using the same AWS CLI workflow.