Build a Local CALDERA Lab
Use CALDERA and Docker to run, contain, and validate a local agent.
Introduction
30 Second Summary
A security response feels complete when an intruder's next move fails. Practising that moment on your everyday computer needs a target you can discard safely.
In this project, you will build a local attack-and-containment lab where Apache CALDERA controls a disposable Alpine Linux target through Sandcat. You will prove the workflow by observing whoami return root before containment blocks the same request.
What You'll Build
Your finished lab lets you watch whoami return root from an isolated target before containment makes the same request stop producing output.
By the end of this project, you'll have:
- A manual operation baseline that shows zero decisions when no agent is available.
- A controlled execution path where a trusted agent runs whoami on the disposable target. Its returned output shows root.
- A containment proof where killing the agent blocks the same command. The stopped target also disappears from the running-container list.
- Secret Mission: Restart mycontainer to prove the killed agent does not persist. Redeploy Sandcat intentionally to restore command execution.
Are there any prerequisites?
Your Mac needs Docker Desktop running before you begin. No cloud account is required because the lab targets only the disposable container on your trusted local network.
Before We Start
Before any hands-on work, commit to an isolated local Apache CALDERA lab that targets only the disposable Alpine Linux container created in this project. That safety boundary protects the server from public access while keeping adversary-emulation activity away from systems you do not own.
Set Up the Local Lab
A safe adversary-emulation lab needs a local control server. It also needs a disposable target that you can discard after the exercise.
In this step, you'll use Docker Desktop to run Apache CALDERA on your Mac. You'll prepare a separate Alpine Linux container as the lab's only target.
In this step, get ready to:
- Launch the local CALDERA control server.
- Sign in with the generated red credential.
- Prepare the disposable target with verified curl support.
Launch CALDERA locally
CALDERA runs in the foreground so its startup logs remain visible. The first macOS Terminal session stays attached to that server throughout the project.
- Press Cmd+Space on your Mac to open system search.
- Type Terminal into the search field.
- Press Enter to open Terminal.
- Confirm Docker Desktop is running by running this command:
docker ps
What does this command do?
The command asks Docker for its running-container list. A successful response confirms that the Docker engine is available, even when the list is empty.
Docker not responding?
- Press Cmd+Space to open system search.
- Type Docker Desktop into the search field.
- Press Enter to open Docker Desktop.
- Wait until Docker Desktop reports that it is running.
- Repeat the Docker check above.
- Still stuck? Help me check why Docker Desktop is not responding on my Mac.
Keep This Lab Local
CALDERA's web interface is not hardened for internet exposure. Port 8888 belongs only to this trusted local exercise.
- Keep your Mac connected to a trusted local network.
- Use mycontainer as the lab's only target.
The next command maps host port 8888 to the same port inside the CALDERA container. Your Terminal remains occupied while the server runs.
The first launch may sit quietly while Docker downloads the image. That pause is expected.
- Start CALDERA in the foreground by running this command:
docker run -p 8888:8888 ghcr.io/apache/caldera:latest
What does this command do?
- The docker run command creates a new container from ghcr.io/apache/caldera:latest.
- The -p 8888:8888 mapping makes the interface available through port 8888 on your Mac.
- The foreground process keeps CALDERA running while its logs remain visible in this Terminal window.
- Wait for the logs to show Log into Caldera with the following admin credentials:.
- Find the generated password listed for red.
- Copy the generated red password to your clipboard.
- Leave this Terminal window open.
CALDERA not starting?
- Confirm that Docker Desktop remains running.
- Check whether another local service already occupies port 8888.
- Stop the conflicting local service before repeating the CALDERA launch.
- Still stuck? Help me troubleshoot the CALDERA container startup.
Sign in to the local interface
The local web address connects Safari to the CALDERA server running on your Mac. The generated red password grants administrative access to this isolated lab.
- Press Cmd+Space on your Mac to open system search.
- Type Safari into the search field.
- Press Enter to open Safari.
- Click Safari's address bar.
- Enter http://localhost:8888.
- Press Return to load the local interface.
- Enter red in the username field.
- Paste the generated red password into the password field.
- Press Enter to submit the sign-in form.
Which Password Should I Use?
The container creates a secure random password during its first startup. That generated password is the correct credential for the red account.
The older red/admin combination does not apply to this container launch.
You now have the control side of your lab working. CALDERA is serving its interface locally through Safari.
- Frame a screenshot so it shows only the signed-in CALDERA interface.
Unable to sign in?
- Confirm that the foreground CALDERA Terminal still shows a running process.
- Copy the password listed specifically for the red account.
- Remove any accidental spaces from the password field.
- Still stuck? Help me troubleshoot my local CALDERA sign-in.
Prepare the disposable target
The Alpine container acts as the endpoint that CALDERA controls later. Its isolated shell keeps every target-side command away from your Mac host.
- Switch back to Terminal.
- Press Cmd+N to create a second Terminal window.
- Create the disposable target by running this command:
docker run --name mycontainer -d -i -t alpine /bin/sh
What does this command do?
- The command creates a container from the alpine image.
- The --name mycontainer option assigns the target a stable name.
- The -d -i -t flags keep an interactive session available while the container runs in the background.
- The /bin/sh command starts the target's shell.
Docker prints a container identifier when the target starts. That output confirms that Docker created mycontainer.
Before you check, predict whether Docker currently tracks one running container or two.
- Verify that both lab containers are running by running this command:
docker ps
What Should You See?
You should see one running container using ghcr.io/apache/caldera:latest. You should also see a running container named mycontainer.
Those two rows prove that the local control server and disposable target are available at the same time.
- Open an interactive shell inside the target by running this command:
docker exec -it mycontainer sh
What does this command do?
The command starts an interactive shell inside the running mycontainer target. Every command entered at the new prompt runs inside the disposable container.
- Install curl inside the target by running this command:
apk add curl
What does this command do?
The Alpine package manager installs the curl package inside mycontainer. CALDERA's generated Linux deployment command can use this tool to retrieve Sandcat later.
- Verify the installed curl command by running:
curl --version
What Should You See?
You should see curl version details in the target shell. This confirms that the package is installed inside mycontainer.
Target setup not working?
- Confirm that the current shell prompt belongs to mycontainer.
- Repeat the interactive-shell command above if the target shell closed.
- Confirm that Docker Desktop still lists mycontainer as running.
- Still stuck? Help me troubleshoot the disposable Alpine target.
Lab Checkpoint
Your CALDERA container is running from ghcr.io/apache/caldera:latest with host port 8888 mapped to container port 8888. The signed-in interface remains available at http://localhost:8888.
Your Alpine target is running as mycontainer with /bin/sh as its command. The open target shell has a verified curl installation.
Both sides of your isolated lab are ready. Next, you'll create a manual operation before any agent connects so you can see exactly what that missing execution channel changes.
Create a No-Agent Baseline
Your local Apache CALDERA control server is running. The disposable Alpine Linux target is ready for an isolated exercise.
Before any agent is deployed, you need a record of what CALDERA can accomplish alone. This baseline operation makes the missing execution channel visible.
In this step, get ready to:
- Configure a manual operation with no adversary.
- Start the operation before deploying an agent.
- Inspect the operation for execution activity.
Configure the manual baseline
A CALDERA operation records execution decisions plus returned results. Manual mode leaves command selection with you.
- Switch back to the Safari tab that is already signed in to your local CALDERA interface.
- Select Operations from the CALDERA navigation.
- Select New Operation.
- Enter baseline-without-agent in the Operation Name field.
- Select No Adversary (manual).
- Keep All groups selected in the Group field.
- Select Run immediately.
Start the operation without an agent
Your operation settings are ready. Starting it now tests whether CALDERA can produce executable work without a connection to the target.
Before you select Start, do you think CALDERA can create any executable work without an agent?
- Start the baseline operation by selecting Start.
CALDERA creates baseline-without-agent with zero decisions. You will find no agent links or command output.
Why the Empty Baseline Matters
An agent carries commands from CALDERA to a target. Without that channel, CALDERA has no endpoint that can receive work.
The empty operation is your control result. It gives later execution evidence a clear comparison point.
Verify the empty baseline
Before you inspect the record again, which signs would prove that no command reached the disposable target?
- Select baseline-without-agent on the Operations page.
- Read the decision count for the selected operation.
- Inspect the operation table for agent link rows.
- Check whether any link offers View Output.
You should see zero decisions with no agent links. There should be no command output to view.
Seeing Decisions or Output?
- Confirm that baseline-without-agent is the selected operation.
- Confirm that the operation uses No Adversary (manual).
- Confirm that the Group field shows All groups.
- Still seeing unexpected activity? Help me inspect my CALDERA baseline operation.
Your control case is locked in. Next, you will add the controlled execution channel that this operation is missing.
Connect the Disposable Target
Your baseline-without-agent operation made the missing execution channel visible. The missing agent leaves Apache CALDERA with no target that can receive a command.
A Sandcat agent creates that controlled channel from mycontainer back to CALDERA. You will connect it through Docker Desktop's host alias.
In this step, get ready to:
- Configure a Linux Sandcat deployment for the disposable target.
- Run the generated deployment command inside mycontainer.
- Confirm Sandcat is trusted and alive in group red.
Configure the Sandcat deployment
Containers use Docker Desktop's host.docker.internal alias to reach services running on the Mac. Setting app.contact.http to http://host.docker.internal:8888 gives Sandcat a route to your local CALDERA server.
- In Safari, return to the local CALDERA interface.
- Select Agents in the left navigation.
- Select Deploy an agent.
- Under Choose an agent, choose Sandcat.
- Under Platform, select linux.
- In Configuration, find app.contact.http.
- Replace its generated value with http://host.docker.internal:8888.
- Select Copy to copy the generated Linux command.
Why This Address Works
Inside mycontainer, localhost identifies the target container itself. Docker Desktop resolves host.docker.internal to the Mac's internal IP.
That alias lets Sandcat reach the CALDERA service exposed on port 8888.
Run Sandcat inside the target
The generated Linux command downloads Sandcat inside the target. It starts the agent in group red.
The running process keeps its shell occupied while Sandcat sends check-ins to CALDERA.
- Return to the interactive mycontainer shell in the second Terminal session from earlier.
- Paste the copied Linux command into the shell prompt.
- Press Enter to start Sandcat.
- Leave this Terminal session running.
Confirm the live agent
CALDERA adds an agent row after Sandcat checks in from the target. The table turns the running process into visible evidence of the connection.
Before you refresh, do you expect the empty Agents table to stay empty or gain a row from mycontainer?
- Return to the Agents page in Safari.
- Refresh the Agents page every few seconds until a new row is listed.
You should see one linux Sandcat agent in group red with the statuses alive and trusted.
You have closed the missing-channel gap. CALDERA can now see a trusted agent inside your disposable target.
Agent Row Not Appearing?
- Check that the generated Sandcat command is still running in the second Terminal session.
- Set app.contact.http to http://host.docker.internal:8888 in Deploy an agent before copying a fresh command.
- Still stuck? Help me troubleshoot why my Sandcat agent is not appearing in CALDERA.
Your disposable target now has a controlled execution channel to CALDERA. Next, you will use it to run one harmless command whose returned output proves the channel works.
Run and Observe a Harmless Command
Your execution channel is live. Apache CALDERA now sees a trusted, alive Sandcat agent inside the disposable Alpine Linux target.
A manual operation can now turn your request into an observable result from the target. This closes the gap exposed by baseline-without-agent.
You will run whoami through the agent. You will inspect the command record before checking the returned username.
In this step, get ready to:
- Create the discovery-with-agent manual operation for group red.
- Submit whoami through the Sandcat agent's sh executor.
- Inspect the submitted command before revealing its returned output.
Create the agent-backed operation
The baseline operation proved that a request needs an agent to reach a target. A separate operation keeps your successful command evidence isolated from that empty baseline.
- In Safari, switch from Agents to Operations.
- Select New Operation.
- Enter discovery-with-agent in Operation Name.
- Select No Adversary (manual).
- Select red in Group.
- Select Run autonomously.
- Select Run immediately.
- Select Start.
You have the comparison point you need. You'll see discovery-with-agent running for group red.
Submit the identity check
The whoami command reads the username associated with the target's current effective user ID. This gives you a low-impact way to prove that the execution channel works.
The sh executor sends the request through the target's shell. CALDERA records that request as a command link inside the operation.
- Select discovery-with-agent in the operations list.
- Select Manual Command.
- Select the alive Sandcat agent from the agent selector.
- Select sh from the executor selector.
- Enter whoami in the command field.
- Submit the request by selecting Add Command.
- Wait for the new command link to complete.
You'll see a completed link in the operation table. That link is CALDERA's record of the request sent through your Sandcat agent.
Inspect the recorded result
CALDERA stores the submitted command separately from its returned output. Checking both sides confirms exactly what the agent ran before you trust the result.
- Select View Command for the completed link.
You'll see whoami as the submitted command. This confirms that the recorded request matches the identity check you entered.
Before you reveal the output, make a quick prediction about the username the Alpine target returned.
- Select View Output for the completed whoami link.
You'll see root in the output. This is the username returned by the disposable target.
You now have direct evidence that CALDERA sent your command through Sandcat. The target completed the request before returning its result.
No command output?
- Wait for the command link to finish before selecting View Output.
- Return to Agents to confirm the Sandcat status is alive.
- Switch back to the existing mycontainer shell to confirm the generated Sandcat deployment command is still running.
- Help me troubleshoot the missing command output.
Your lab now has a complete request-to-result trail from whoami to root.
Next, you will remove the execution channel. You will repeat the same request to prove containment.
Contain the Agent and Prove the Block
Your CALDERA operation now holds a completed whoami result from the disposable target. You have clear evidence that the execution channel works.
A successful command proves access at one moment. Containment requires evidence that the same channel can no longer deliver commands.
The proof comes from killing Sandcat before a repeat whoami request. Stopping the target container closes the remaining runtime.
In this step, get ready to:
- Kill the active Sandcat agent through CALDERA.
- Repeat the harmless command after containment.
- Stop the disposable target and verify that it is absent from the running-container list.
Kill the active agent
An agent with the alive status provides an available execution channel. CALDERA can terminate that channel through Kill all agents.
This action targets the disposable Sandcat process. Your CALDERA server remains running with the earlier operation evidence intact.
- Switch back to Agents in CALDERA.
- Select Bulk Actions.
- Select Kill all agents.
- Return to the Terminal session where the Sandcat deployment command was running.
The Sandcat command ends in the target Terminal session. This confirms that the agent process received the kill request.
- Refresh the Agents page.
You should see no agent with the alive status. Good, the target no longer has an active CALDERA execution channel.
What Did the Kill Action Contain?
CALDERA sent a termination request to the active Sandcat process. The agent stopped beaconing from mycontainer.
The existing operation history remains available. You can still use its earlier root output as evidence of execution before containment.
Agent Still Showing as Alive?
- Refresh the Agents page to load the current status.
- Confirm that you selected Kill all agents from Bulk Actions.
- Still stuck? Help me confirm why my Sandcat agent remains alive after using CALDERA Bulk Actions.
Repeat the command after containment
The earlier root output remains your before-containment result. A repeat request tests whether the killed channel can still execute the same action.
- Return to Operations.
- Select discovery-with-agent.
- Select Manual Command.
- Select the previous Sandcat agent in the agent selector.
- Select sh in the executor selector.
- Enter whoami in the command field.
Before you select Add Command, do you expect the killed agent to return another username?
- Submit the repeat request by selecting Add Command.
- Compare the repeat link with the earlier completed link.
The earlier link still provides View Output with root. The repeat request produces no new View Output result because no agent is alive to execute it.
That missing output is your strongest containment evidence so far. The same operator request has lost its execution channel.
Did the Repeat Command Return Output?
- Return to Agents to confirm that no row has the alive status.
- Use Kill all agents again if another agent is active.
- Still seeing new output? Help me determine why a repeated CALDERA command executed after containment.
Stop the disposable target
The agent channel is closed while the target container is still available. Stopping the Docker container removes that remaining runtime from the lab.
Stopping the target preserves its container filesystem. The CALDERA container continues running in its separate foreground Terminal session.
- Switch back to the Terminal session connected to mycontainer.
- Return to the macOS shell prompt by leaving the target shell with this command:
exit
What Does This Command Do?
The exit command closes the current container shell session. You return to the macOS prompt where the Docker CLI can manage mycontainer.
You should now see your normal macOS shell prompt. The prompt no longer belongs to the Alpine target.
- Stop the disposable target by running this command:
docker stop mycontainer
What Does This Command Do?
This stop request ends the runtime for mycontainer. Its filesystem still retains the installed curl package.
Before you check the running-container list, do you expect mycontainer to appear beside CALDERA?
- Confirm the target is absent from the running-container list by running this command:
docker ps
What Does This Command Prove?
This listing shows the containers that are currently running. You should still see the CALDERA container.
You should not see mycontainer anywhere in the list. Its absence proves that the disposable target is stopped.
You now have the full containment proof. The earlier root result proves execution before containment.
The repeat request has no output. The target no longer appears in the running-container list.
Is mycontainer Still Listed?
- Use the macOS shell prompt for Docker commands.
- Check that the container name is spelled exactly as mycontainer.
- Still listed? Help me stop my disposable Alpine container and verify that it is absent from Docker's running-container list.
Secret mission
Test Recovery Without Accidental Persistence
Restart the stopped target and test whether the killed agent returns on its own. Then redeploy Sandcat deliberately and prove that command execution only resumes after you restore the control channel.
Clean Up Your Resources
Clean Up Your Resources
Your local lab has no cloud or metered API costs under qualifying Docker Desktop use. Choose whether to keep both containers active, pause them for later, or delete them with their ephemeral lab data.
Resources you used:
- The running Apache CALDERA container created from ghcr.io/apache/caldera:latest with port 8888 mapped to your Mac.
- The ephemeral configuration, operation history, command results, plus agent records stored inside the CALDERA container.
- The running Alpine Linux target named mycontainer. It contains curl plus the intentionally redeployed Sandcat process.
Keep everything running
No action is needed. Choose this only while you are actively using the trusted local lab.
- Keep the CALDERA foreground Terminal session running so http://localhost:8888 stays available on your Mac.
- Keep mycontainer running so the redeployed Sandcat agent remains alive.
- Keep port 8888 limited to this trusted local exercise.
Pause - I'll come back to this later
Stop both containers to free local resources while keeping their files available. You can restart the lab when you are ready to continue.
- Switch to a macOS Terminal prompt outside mycontainer.
- List the running containers by running this command:
docker ps
What Does This Show?
This command lists the containers currently running on your Mac. The CALDERA row uses the ghcr.io/apache/caldera:latest image.
- Find the container ID in the CALDERA row.
- Record its ID here: <CALDERA_CONTAINER_ID>.
- Pause both lab containers by running these commands:
docker stop [[CALDERA_CONTAINER_ID="<CALDERA_CONTAINER_ID>"]]
docker stop mycontainer
What Do These Commands Do?
The first docker stop command ends the CALDERA server process. The foreground CALDERA Terminal session exits.
Stopping mycontainer also ends the live Sandcat process. Both container filesystems remain available for restart.
- Resume both containers later by running these commands:
docker start [[CALDERA_CONTAINER_ID="<CALDERA_CONTAINER_ID>"]]
docker start mycontainer
What Happens After Restart?
The CALDERA interface becomes reachable again through http://localhost:8888. Its container still holds your operation history.
The target container starts without automatically restoring Sandcat. Intentional redeployment is required because you added no restart policy or startup script.
Delete - I don't want to use this again
Deleting both containers permanently removes the lab state you created. The downloaded CALDERA plus Alpine images remain available for another lab.
- Switch to a macOS Terminal prompt outside mycontainer.
- List the running containers by running this command:
docker ps
Find the CALDERA Container
The output identifies each running container. Use the row with the ghcr.io/apache/caldera:latest image to find the CALDERA container ID.
- Record the CALDERA container ID here: <CALDERA_CONTAINER_ID>.
- Stop both containers before removing them by running these commands:
docker stop [[CALDERA_CONTAINER_ID="<CALDERA_CONTAINER_ID>"]]
docker stop mycontainer
docker rm [[CALDERA_CONTAINER_ID="<CALDERA_CONTAINER_ID>"]]
docker rm mycontainer
What Gets Deleted?
The docker stop commands end both running processes. The docker rm commands delete the stopped containers.
Removing the CALDERA container deletes its ephemeral configuration, operation history, command results, plus agent records. Removing mycontainer deletes the disposable target filesystem.
You should see each container identifier printed after its command succeeds. The downloaded images stay on your Mac.
- Verify that neither lab container remains running by running this command:
docker ps
What Should You See?
The output should contain no row for the CALDERA image. It should also contain no row named mycontainer.
Your local attack-and-containment lab is now shut down. Port 8888 is no longer serving the CALDERA interface.
Nice Work!
Nice Work!
Mission complete! You built a local adversary emulation lab with Apache CALDERA inside Docker. The lab now demonstrates a complete attack-to-validation workflow against the disposable mycontainer target.
You've learned how to:
- Reveal the designed obstacle with baseline-without-agent. Its zero decisions show that an operation needs an agent before commands can run.
- Create controlled execution by deploying a trusted Sandcat agent to the disposable Alpine Linux target. Confirm the result by finding root under View Output for whoami.
- Prove manual containment by killing the agent process. Confirm the block when a repeated whoami command produces no new output. Stop mycontainer to remove the disposable target from the running-container list.
- Complete the optional Secret Mission by restarting mycontainer without restoring the killed agent. Redeploy Sandcat intentionally. Verify that a fresh whoami command returns root again.
Ready to quiz yourself?