Running EC2 Scripts Locally with LocalStack
LocalStack gave me mock EC2 instances in minutes, but only a plain Ubuntu container in Docker actually ran my bash scripts. This post covers the full path on a MacBook, including the dead ends.

The short version
I wanted a practice "server" on my own laptop where I could run bash scripts without paying for real Amazon cloud. I started with just my MacBook, Docker and a tool called LocalStack, which pretends to be Amazon's cloud on your own computer. I used it to create a fake EC2 server, and the dashboard said it was running, but I found out it was only a label with no real machine behind it, so my script could not run on it. I tried a setting that should have made LocalStack build real machines, but none appeared, possibly because of my free plan. In the end I used Docker directly to start a real Ubuntu Linux machine, ran my script on it, and it printed "hello from EC2". Now I have a working place to practise scripts.
Setup: what I had running
LocalStack runs as one Docker container on your machine and answers AWS API calls on port 4566. I used a MacBook Air with Docker Desktop, the AWS CLI, the awslocal wrapper and the LocalStack CLI (version 2026.8.2).
Start LocalStack in the background and confirm the container is healthy:
localstack start -d
docker ps --format '{{.Names}} {{.Status}}'
You should see one container named localstack-main with a (healthy) status. The LocalStack web app at app.localstack.cloud then shows your instance under Resource Browser, including an EC2 page.
The AWS CLI refuses to send any request without credentials, even to LocalStack. LocalStack does not check them, so dummy values are enough. Set them once per terminal:
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
export AWS_PAGER=""
The last line stops the CLI from opening long output in a pager, which otherwise traps you on an (END) or : prompt until you press q. Note that :q is a vim command and does nothing in a terminal pager.
Step 1: Create mock EC2 instances
A fresh LocalStack has no images of your own. Register one, then launch an instance from it. I also wrote a small user-data script to see whether it would run at launch:
awslocal ec2 describe-images --owners self
awslocal ec2 register-image \
--name "localstack-practice" \
--architecture x86_64 \
--root-device-name /dev/sda1 \
--virtualization-type hvm
cat > myscript.sh <<'EOF'
#!/bin/bash
echo "hello from EC2" > /tmp/hello.txt
EOF
aws --endpoint-url=http://localhost:4566 ec2 run-instances \
--image-id ami-ubuntu-practice \
--instance-type t2.micro \
--user-data file://myscript.sh \
--region us-east-1
The first command returned an empty list, and register-image returned an ID (mine was ami-390225de4ea68f2eb). run-instances printed a full instance record with State set to pending, and the Resource Browser listed the instance as running shortly after.
That looks like success, and for the EC2 API it is: you can describe, stop, start and terminate these instances, and tools such as Terraform or the AWS CLI will treat them as real. But at this point nothing is running anywhere. The instance is a database record, so the user-data script never executed and there is no machine to log in to.
To see this for yourself, run docker ps. If the only container is localstack-main, your instances are mock records.
Step 2: Try to make the instances real
LocalStack can back an EC2 instance with a Docker container if you set EC2_VM_MANAGER=docker. LocalStack looks for a local Docker image tagged localstack-ec2/<ami-name>:<ami-id>, so I pulled Ubuntu and tagged it:
docker pull ubuntu:24.04
docker tag ubuntu:24.04 localstack-ec2/ubuntu-practice:ami-ubuntu-practice
Then I restarted LocalStack with the setting and checked that it reached the container and that the Docker socket was mounted:
localstack stop
localstack start -d -e EC2_VM_MANAGER=docker
docker inspect localstack-main | grep EC2_VM_MANAGER
docker inspect localstack-main --format '{{json .Mounts}}' | grep docker.sock
Both checks passed. The restart also wiped my earlier instances, so I had to launch again.
My first two launches used ami-ubuntu-practice and ami-000001. Neither was a registered image, which my describe-images output confirmed, so they were never going to work. I then registered a proper image, tagged Ubuntu with the ID it returned, and launched from that:
AMI_ID=$(awslocal ec2 register-image \
--name ubuntu-practice \
--architecture x86_64 \
--root-device-name /dev/sda1 \
--virtualization-type hvm \
--query ImageId --output text)
docker tag ubuntu:24.04 localstack-ec2/ubuntu-practice:$AMI_ID
awslocal ec2 run-instances \
--image-id $AMI_ID \
--instance-type t2.micro \
--user-data file://myscript.sh
The API returned success, but docker ps still showed only localstack-main, and the LocalStack logs recorded RunInstances => 200 with no Docker activity. My license showed as freemium, and I suspect Docker-backed EC2 needs a higher tier, but I could not confirm that. If you have a paid LocalStack plan, this route may work for you, so check the logs with docker logs localstack-main for the reason.
Step 3: Run scripts in a real container
When LocalStack would not start a machine, I started one myself with plain Docker. A container running sleep infinity stays alive and behaves like a small Linux VM:
docker run -d --name myvm ubuntu:24.04 sleep infinity
docker exec -i myvm bash < myscript.sh
docker exec myvm cat /tmp/hello.txt
The last command printed hello from EC2, which proves the script ran inside the container and wrote its file there. The < myscript.sh part feeds the script from your Mac into bash inside the container, so you can edit scripts locally and rerun them at will.
This container is not linked to the mock instances in LocalStack. Use LocalStack for the EC2 API and myvm for running code.
Day-to-day commands for the container:
| Task | Command |
|---|---|
| Run a script file | docker exec -i myvm bash < myscript.sh |
| Open an interactive shell | docker exec -it myvm bash |
| Copy a file in | docker cp myscript.sh myvm:/root/myscript.sh |
| Copy a file out | docker cp myvm:/tmp/hello.txt ./hello.txt |
| Stop and start it | docker stop myvm and docker start myvm |
| Delete it | docker rm -f myvm |
| Install tools inside | docker exec myvm bash -c "apt-get update && apt-get install -y curl nano" |
Files you write inside the container survive docker stop and docker start, but are lost when you run docker rm. The ubuntu:24.04 image is minimal, so install anything you need first.
Troubleshooting: errors I hit
Every one of these cost me time, so here is the cause and the fix in one place.
| What I saw | Cause | Fix |
|---|---|---|
NoCredentials: Unable to locate credentials |
The AWS CLI wants credentials even for LocalStack | export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test |
Terminal stuck on (END) or : |
The AWS CLI opened output in a pager | Press q, then export AWS_PAGER="" |
zsh: no such file or directory: container_id |
I typed <container_id> literally, and zsh read < as a file redirect |
Use the real container name from docker ps |
No such container: NEW_CONTAINER_NAME |
Same mistake with a placeholder name | Replace it with myvm or the real name |
Instance shows running but docker ps has only localstack-main |
The instance is a mock record with no machine behind it | Use a plain Docker container (Step 3) |
Image ID not in describe-images |
I launched with an AMI name I had never registered | Register it with register-image and use the ID it returns |
| Instances vanished after a restart | LocalStack was restarted without persistence | Relaunch them, or script the launch so it is repeatable |
Takeaways
A running state in LocalStack does not mean a machine exists. Check docker ps before you assume you can log in or run scripts.
Use LocalStack to practice the EC2 API: registering images, launching, describing, stopping and terminating instances.
Use a plain Docker container to practice what runs on a server: bash scripts, package installs and file handling.
Docker-backed EC2 in LocalStack may need a paid plan. I could not make it work on a
freemiumlicense, but did not confirm that was the cause.Scripts passed with
--user-dataonly run if a real container backs the instance.
The next step I would try is a launch script that creates the container and registers a matching mock instance together, so the two stay in sync.
Getting back to your machine every day: command cheat sheet
After a restart, open Docker Desktop and wait until it says it's running. Your myvm container is set to restart on its own, and anything you saved inside it is still there. The only thing that deletes it is docker rm.
Daily routine
docker ps # check myvm is running
docker start myvm # only if it isn't listed
docker exec -it myvm bash # step inside (type exit to leave)
Write and run scripts from your Mac
nano myscript.sh # write or edit the script
docker exec -i myvm bash < myscript.sh
docker exec myvm cat /tmp/hello.txt # check what it created
Move files
docker cp myscript.sh myvm:/root/myscript.sh # Mac to machine
docker cp myvm:/tmp/hello.txt ./hello.txt # machine to Mac
Install tools in the machine (once)
docker exec myvm bash -c "apt-get update && apt-get install -y nano curl"
Manage the machine
docker stop myvm
docker start myvm
docker rm -f myvm # deletes it and everything inside
docker run -d --name myvm ubuntu:24.04 sleep infinity # recreate if deleted
docker update --restart unless-stopped myvm # auto-start on Docker launch
LocalStack dashboard (optional)
You only need this for practising the EC2 API. Your scripts run in myvm without it. Mock instances are wiped each time LocalStack restarts, so launch new ones.
localstack start -d
export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1 AWS_PAGER=""
localstack stop
About the author
- 2nd Year IT Student | AI Agents • Open Source • DevOps • MLOps • Cloud Infrastructure | Building scalable, reliable systems.
Comments
Join the discussionNo comments yet. Be the first to comment.

