Free‑tier cloud credits run out faster than expected – a single Jupyter notebook on a GPU spot instance can consume a 10 GB quota in under an hour. The bottleneck is not the model, it is the time spent wiring containers, mounting data, and reproducing the same environment on a new VM. ## 1. Define the experiment once, deploy everywhere
The most reliable way to keep the environment consistent across GCP Free Tier, Azure for Students, and AWS Free Tier is to describe it in a docker-compose.yml. Write the file once, then spin up a replica with a single command.
version: "3.9"
services:
notebook:
image: jupyter/datascience-notebook:latest
ports:
- "8888:8888"
volumes:
- ./work:/home/jovyan/work
environment:
- GRANT_SUDO=yes
command: start-notebook.sh --NotebookApp.token=''
tensorboard:
image: tensorflow/tensorflow:2.13.0-jupyter
ports:
- "6006:6006"
volumes:
- ./logs:/logs
command: tensorboard --logdir=/logs --host=0.0.0.0
python
Takeaway: Store the compose file in your repo; a single docker compose up -d reproduces the exact stack on any free‑tier VM. ## 2. Automate file generation with a Python helper
Manually editing YAML for each experiment is error‑prone. The snippet below generates a compose file based on a tiny JSON spec. It works on a laptop without extra dependencies.
import json, pathlib
spec = {
"services": {
"notebook": {
"image": "jupyter/datascience-notebook:latest",
"ports": ["8888:8888"],
"volumes": ["./work:/home/jovyan/work"],
"environment": ["GRANT_SUDO=yes"],
"command": "start-notebook.sh --NotebookApp.token=''"
},
"tensorboard": {
"image": "tensorflow/tensorflow:2.13.0-jupyter",
"ports": ["6006:6006"],
"volumes": ["./logs:/logs"],
"command": "tensorboard --logdir=/logs --host=0.0.0.0"
}
}
}
out = pathlib.Path('docker-compose.yml')
out.write_text('version: "3.9"\nservices:\n')
for name, cfg in spec["services"].items():
out.write_text(out.read_text() + f' {name}:\n')
for k, v in cfg.items():
if isinstance(v, list):
out.write_text(out.read_text() + f' {k}:\n')
for item in v:
out.write_text(out.read_text() + f' - {item}\n')
else:
out.write_text(out.read_text() + f' {k}: {v}\n')
print('docker-compose.yml generated')
Run it with python gen_compose.py. The script can be extended to accept command‑line arguments for GPU count, Python version, or additional services. Takeaway: A few lines of Python replace repetitive YAML edits and keep the spec source‑controlled. ## 3. And deploy on three free‑tier providers without modification
GCP Free Tier (e2‑micro + Cloud Shell)
# In Cloud Shell
git clone https://github.com/yourname/ml‑experiment.git && cd ml‑experiment
python gen_compose.py
docker compose up -d
The e2‑micro instance has only 0.6 vCPU, but the notebook runs fine for data‑preprocessing and small‑scale training. ### Azure for Students (B1S VM)
az vm create --resource-group ml-demo --name ml‑vm \
--image UbuntuLTS --size B1s --generate-ssh-keys
az vm open-port --resource-group ml-demo --name ml‑vm --port 8888
ssh azureuser@<public-ip>
# inside VM
git clone https://github.com/yourname/ml‑experiment.git && cd ml‑experiment
python3 -m venv .venv && source .venv/bin/activate
pip install docker-compose
python gen_compose.py
docker compose up -d
The B1S VM offers 1 vCPU and 1 GiB RAM, enough for a notebook that uses CPU‑only libraries. ### AWS Free Tier (t2.micro EC2)
aws ec2 run-instances --image-id ami-0c02fb55956c7d316 \
--instance-type t2.micro --key-name mykey --security-group-ids sg-12345 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ml‑vm}]'
# Add inbound rules for 8888 and 6006 in the console
ssh -i mykey.pem ec2-user@<public-ip>
# inside EC2
sudo yum install -y docker
sudo systemctl start docker
sudo usermod -aG docker ec2-user
newgrp docker
git clone https://github.com/yourname/ml‑experiment.git && cd ml‑experiment
python3 -m venv .venv && source .venv/bin/activate
pip install docker-compose
python gen_compose.py
docker compose up -d
All three clouds start the same containers, mount the same host directories, and expose identical ports. No provider‑specific Dockerfiles are needed. Takeaway: A single compose file and generator script let you move between free‑tier VMs with copy‑paste commands. ## 4. Keep data cheap and persistent across runs
Free tiers recycle the root volume after a stop/start cycle. Store experiment artefacts in a mounted bucket instead of the VM filesystem. * GCP: gcsfuse my-bucket ./work
- Azure:
blobfuse ./work --container-name=mycontainer --tmp-path=/mnt/blobfusetmp - AWS:
s3fs my-bucket ./work -o iam_role=auto
Mount the bucket before docker compose up. The containers see the same path (./work) whether they run locally or on a cloud VM, and the data never counts against the VM quota. Takeaway: Bind a cloud storage fuse mount to the compose volume; you stay within free compute credits while keeping unlimited data. ## 5. Validate the environment automatically
A quick health check prevents wasted credits. Add a one‑off service that runs pip list inside the notebook container and exits with status 0 only if the expected packages are present.
validator:
image: jupyter/datascience-notebook:latest
command: >
bash -c "pip list | grep -E 'numpy|pandas|scikit-learn' && exit 0 || exit 1"
depends_on:
- notebook
Run docker compose up validator. If the command returns non‑zero, the CI pipeline (or a simple shell script) can abort the experiment before any GPU minutes are burned. Takeaway: A minimal validator service catches missing dependencies early, protecting scarce free credits. Act today: Clone the repo, run python gen_compose.py, then launch docker compose up -d on your local machine. The same commands will work unchanged on any free‑tier VM, giving you an instant, reproducible ML sandbox without spending a single credit.
If this was useful: I packaged a deeper version as LinkedIn Outreach Sequence Prompt Pack.
This article was drafted with AI assistance.
Top comments (0)