A Maven WAR through CodeArtifact, CodeBuild, CodeDeploy and CodePipeline on Floci in Docker. No AWS account, no bill.
To run an AWS CI/CD pipeline with no AWS account, I pointed the AWS CLI at Floci, a local AWS emulator, and scripted every stage in PowerShell. The pipeline builds a Java web app, deploys it to an "EC2 instance" that is really a Docker container, and serves it on localhost. Most of it worked as written. CodeDeploy hooks did not, so I wrote a small SSM agent stand-in to run them.
Repo: axololtos/cicd-pipeline-floci. The design follows the free NextWork DevOps challenge; the repo is not affiliated with it.
Why emulate it
The tutorial version needs a real account, IAM setup, a running EC2 instance and careful cleanup. Miss one resource and you pay for it. Floci removes that risk, and the whole thing is repeatable: 07-cleanup.ps1 tears it down and the numbered scripts rebuild it.
The trade-off is fidelity. Floci implements the API surface, not every behavior behind it. Most of this post is about where that gap showed up.
Architecture
| Stage | Service | Role |
|---|---|---|
| Source | GitHub | Hosts the Maven webapp |
| Packages | CodeArtifact | Maven repo that mvn deploy publishes to |
| Build | CodeBuild | Builds the WAR, uploads it to S3 |
| Deploy | CodeDeploy + EC2 + SSM | Hook scripts fetch the WAR and start Tomcat |
| Orchestrate | CodePipeline | Chains Source, Build, Deploy |
Stack: Floci in Docker (with the Docker socket mounted, so EC2 instances become containers), PowerShell 5.1+, AWS CLI v2, Docker Desktop, and a Maven 3.5.2 / JDK 8 build image. The scripts live in scripts/:
01-start-floci.ps1 02-webapp.ps1 03-codeartifact.ps1 04-codebuild.ps1 05-deploy.ps1 06-pipeline.ps1 07-cleanup.ps1 08-screenshots.ps1 env.ps1 ssm-agent.ps1 appspec.yml compose.yaml hooks/ Pointing the CLI at Floci
env.ps1 holds the only AWS configuration: fake credentials and one endpoint.
$env:AWS_ENDPOINT_URL = "http://localhost:4566" $env:AWS_DEFAULT_REGION = "us-east-1" $env:AWS_ACCESS_KEY_ID = "test" $env:AWS_SECRET_ACCESS_KEY = "test" $env:AWS_PAGER = "" AWS_ENDPOINT_URL is the global endpoint override in recent CLI v2 builds, so no command needs --endpoint-url. Any access key works. aws sts get-caller-identity returns account 000000000000, which confirms you are talking to Floci. The same file defines $MavenImage = "maven:3.5.2-jdk-8" and an Mvn helper that runs Maven in a throwaway container, so the host needs no JDK.
First-run output: caller identity, the three IAM roles, both S3 buckets, the CodeArtifact repo.
The same resources in the Floci console:
Build: CodeArtifact and CodeBuild
03-codeartifact.ps1 creates the IAM roles (codebuild-role, codedeploy-role, codepipeline-role), a CodeArtifact domain nextwork and repository nextwork-devops-cicd, then runs mvn deploy from a container. Re-running it prints ConflictException ... already exists, which is harmless.
04-codebuild.ps1 creates two buckets, zips the source with tar.exe so the zip is Linux-safe, uploads it to s3://nextwork-source/source.zip, and creates the build project. It polls until buildComplete is true, then lists the artifact: a 2,291-byte nextwork-web-project.war in nextwork-artifacts.
Maven reported BUILD SUCCESS in 17.269 s, and the build log group shows up in the console:
Floci's IAM does not enforce policies by default, so role ARNs only need to exist. On real AWS, a wrong trust policy fails the build.
Deploy: where the emulator gap showed up
05-deploy.ps1 creates a key pair and a web-sg security group (port 8080), then runs aws ec2 run-instances. Floci starts a container named floci-aws-ec2-<instance-id>; the script installs Corretto 8, Tomcat 9.0.89 and the AWS CLI inside it.
It then registers the instance with SSM, copies scripts/hooks into the container at /scripts, and recreates the CodeDeploy app and group.
Problem 1: tag-based targeting finds nothing
A deployment group normally selects instances by EC2 tag. In Floci that lookup fails, so the script registers the instance as an on-premises instance and gives the group no filters:
aws deploy create-deployment-group --application-name nextwork-app --deployment-group-name nextwork-dg ` --service-role-arn arn:aws:iam::000000000000:role/codedeploy-role aws deploy register-on-premises-instance --instance-name $id ` --iam-user-arn arn:aws:iam::000000000000:user/devops-iam Problem 2: nobody runs the hooks
On real AWS the agent on the instance pulls commands and runs the appspec.yml hooks. Floci queues those commands for an SSM agent, but none exists, and it fails the hook if the command is not acknowledged within about 500 ms.
So ssm-agent.ps1 is a background job started by 05-deploy.ps1. It polls GetMessages every 100 ms, acknowledges each message, base64-decodes the payload, runs it with docker exec <container> sh -c, and sends the result back with SendReply (status, exit code, output). It must start before the deployment is created, and 05 drains stale queued commands first so leftovers from a failed run do not push the real deployment past the ack window.
The hooks
Floci skips the bundle download and install, so the hook scripts fetch the WAR from S3 themselves. The deployment finished Succeeded with both hooks exiting 0, as the agent log above shows.
Floci's port forwarder published the container's 8080 on host port 30000:
The pipeline
06-pipeline.ps1 takes your GitHub username, writes the definition to pipeline.json, and creates the pipeline: Source (GitHub, branch main), Build (nextwork-build), Deploy (nextwork-app / nextwork-dg), with nextwork-artifacts as the artifact store.
aws codepipeline create-pipeline --cli-input-json "file://$f" aws codepipeline start-pipeline-execution --name nextwork-pipeline The first poll shows Build InProgress, the next shows all three stages Succeeded:
The full resource check at the end, via the AWS CLI:
What it does not prove
- CodeBuild reads
s3://nextwork-source/source.zip, not the GitHub artifact. Floci's CodeBuild accepts only S3 or no source. To ship a change, edit the app and rerun04and05. - The pipeline's Deploy stage reports success without running hooks. The hook path I verified is the direct deployment from
05-deploy.ps1. - Floci is in-memory. The build script itself warns that artifacts are lost if Floci restarts.
- CodeArtifact has no upstream resolution, so Maven Central serves plugins.
- IAM is not enforced, so permission bugs stay invisible.
This is a rehearsal of the pipeline's shape and API calls, not proof that the same definition passes on real AWS.
Run it
Needs Docker Desktop, AWS CLI v2, PowerShell 5.1+ and Git. Step 2 needs an empty GitHub repo URL; step 6 needs your GitHub username.
cd scripts Set-ExecutionPolicy -Scope Process Bypass .\01-start-floci.ps1 .\02-webapp.ps1 -RepoUrl https://github.com/YOU/nextwork-web-project.git .\03-codeartifact.ps1 .\04-codebuild.ps1 .\05-deploy.ps1 .\06-pipeline.ps1 -GitHubUser YOU .\07-cleanup.ps1 After 05, open http://localhost:<port>/ using the port it prints (30000 in my run).
Next
- Feed CodeBuild the pipeline's GitHub source artifact instead of the pre-zipped S3 object.
- Make the Deploy stage run the hooks and prove it with a visible page change.
Repo: github.com/axololtos/cicd-pipeline-floci. A star helps if this saved you an AWS bill, and you can follow me here on DEV for more.















