Test LocalStack sur une VM Docker ECloud

Cette page décrit les premiers tests réalisés avec LocalStack sur une VM Docker sur ECloud.
L’objectif est de tester ce qu’on peut faire avec l’outil et comment peut-on le transformer en une ressources pédagogiques.

LocalStack est un émulateur des APIs AWS, Azure et SnowFlake qui permet de gérer des services cloud AWS émulés.
L’émulateur fonctionne principalement dans un conteneur Docker.
L’outil permet d’émuler localement des services AWS comme EC2, S3, Lambda, etc., sans utiliser directement le véritable cloud AWS.
L’utilisation de cet outil offre un gain de temps lors des tests de déploiement. Et puisque les ressources ne sont pas vraiment allouées sur le cloud d’AWS, cela réduit le coût des tests de l’infrastructure avant son déploiement réel. L’interaction avec les services AWS émulés est possible via une approche Infrastructure as Code. Cela permet d’enseigner des technologies tels que Terraform ou OpenTofu sans dépendre d’AWS.

La page est structurée comme suit :

Les services AWS emulés:

1. Contexte de test

Test réalisé sur ECloud avec une VM Docker disposant des ressources suivantes :

  • 4 vCPU
  • 8 Go de RAM
  • 32 Go de disque
  • LocalStack lancé en mode Docker
  • Ecrit en Python et installé dans un environnement virtuel nommé localstack.

Versions LocalStack:

Runtime version  : 2026.3.1.dev4943
LocalStack CLI   : 2026.3.0
Profile          : default
App              : https://app.localstack.cloud
Licence          : Hobby / Free

Fonctionnalités disponible dans l’offre Free (Hobby) :

  • LocalStack for AWS Emulator
  • 30+ services émulés : ec2, s3, route53, rds, opensearch, dynamodb, cloudwatch, … etc.
  • Les services AWS disponibles sont accessibles à travers les ports ouverts sur ce container.

2. Installation de Localstack dans un environnement Python virtuel

sudo apt install python3.11-venv terraform
python3 -m venv ~/localstack
. ~/localstack/bin/activate
pip install localstack awscli-local wheel terraform-local awscli-local boto3

# Il faut créer un compte sur Localstack et récupérer un token.
# Aller sur https://app.localstack.cloud/sign-in

localstack auth set-token $TOKEN
# Démarrage du container docker Localstack
localstack start -d

docker container ls --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"
# localstack-main   127.0.0.1:443->443/tcp, 127.0.0.1:4510-4560->4510-4560/tcp, 53/tcp, 5678/tcp, 127.0.0.1:4566->4566/tcp

3. Différence entre les Clis (command line interfaces)

1. aws et awslocal

Sur la VM, les deux commandes existent dans l’environnement virtuel Python :

which aws # /home/master/localstack/bin/aws

# aws est la CLI officielle AWS. Par défaut, elle essaie de parler au **vrai AWS**.

# Exemple :

# Sans configuration spéciale, cette commande cherche à contacter AWS réel.
aws s3 ls # Unable to locate credentials. You can configure credentials by running "aws configure".
which awslocal # /home/master/localstack/bin/awslocal

# awslocal est un wrapper fourni pour LocalStack. Il appelle la commande aws, mais ajoute automatiquement l'endpoint LocalStack.

# Exemple :

awslocal s3 ls # 2026-04-24 10:09:37 demo-localstack-bucket

# Cette commande est équivalente à :

AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test AWS_DEFAULT_REGION=us-east-1 \
aws --endpoint-url=http://localhost:4566 s3 ls
Pour travailler avec LocalStack, il est donc plus simple d'utiliser awslocal au lieu de répéter --endpoint-url=http://localhost:4566 à chaque commande.

2. Terraform et tflocal

Le même principe est appliqué sur Terraform.


4. Aide et autocomplétion

Afficher l’aide générale

aws help
awslocal help

Afficher l’aide d’un service

aws s3 help
awslocal s3 help

aws lambda help
awslocal lambda help

Afficher l’aide d’une sous-commande

aws s3 ls help
aws s3 cp help

awslocal s3 ls help
awslocal s3 cp help

Activer l’autocomplétion temporairement

complete -C "$(which aws_completer)" aws
complete -C "$(which aws_completer)" awslocal

Après cela, on peut tester :

awslocal s3 <TAB><TAB> # cp        ls        mb        mv        presign   rb        rm        sync      website

Rendre l’autocomplétion permanente

Ajouter dans ~/.bashrc :

if command -v aws_completer >/dev/null 2>&1; then
  complete -C "$(command -v aws_completer)" aws
  complete -C "$(command -v aws_completer)" awslocal
fi

Puis recharger le shell :

source ~/.bashrc


Les services AWS emulés

Pour rappel, ces services ne font qu’une imitation du comportement des veritables services émulés.

1. Amazon Simple Storage Service: S3

Amazon S3 est un service de stockage d’objets. Dans LocalStack, on peut utiliser les API S3 localement pour créer des buckets, envoyer des objets, les lister, les télécharger, tester des URLs présignées ou configurer certains paramètres comme CORS. Il représente des avantages Cloud comme la capacité de mise à l’échelle, la disponibilité ..etc.

On peut le représenter ainsi :

S3
└── bucket
    ├── objet 1
    ├── objet 2
    └── objet 3

Vocabulaire :

  • bucket : conteneur logique
  • object : objet stocké, souvent créé à partir d’un fichier
  • key : nom ou chemin logique de l’objet
  • metadata : informations associées à l’objet, par exemple taille, type de contenu, ETag

Exemple :

s3://demo-localstack-bucket/images/photo.png

On peut voir S3 comme un service de stockage accessible par API HTTP.
S3 ressemble à un système de fichiers parce qu’on écrit des chemins comme : s3://demo-localstack-bucket/images/photo.png
Mais S3 n’est pas un disque monté et ce n’est pas un dossier Linux.
Avec S3, on ne manipule pas des fichiers locaux mais des objets stockés dans un service.
Le / dans images/photo.png ne crée pas forcément un vrai dossier. C’est surtout une convention dans le nom de l’objet.
Les interfaces AWS l’affichent comme un dossier pour faciliter la lecture, mais techniquement S3 stocke des objets avec des clés.

# On ne peut pas faire:
cd s3://demo-localstack-bucket
# Mais on utilise des commandes API :
awslocal s3 ls s3://demo-localstack-bucket/

Il existe des outils comme s3fs qui permettent de monter un bucket S3 comme s’il ressemblait à un dossier local, mais ce n’est qu’une couche d’adaptation. S3 reste un service de stockage objet, avec un comportement différent d’un vrai système de fichiers.

Créer un bucket

Deux styles de commandes sont possibles avec la CLI AWS :

  • awslocal s3 : commandes haut niveau, pratiques pour manipuler des fichiers
  • awslocal s3api : commandes proches de l’API AWS S3, plus précises et plus explicites

Exemple avec la commande haut niveau :

awslocal s3 mb s3://demo-localstack-bucket

Exemple avec l’API S3 :

awslocal s3api create-bucket --bucket demo-localstack-bucket

Dans LocalStack, la région par défaut est généralement us-east-1. Pour un TP, il est utile de garder cette région partout afin d’éviter les différences de comportement entre commandes, SDK et Terraform.

Lister les buckets

awslocal s3 ls
awslocal s3api list-buckets

Exemple de sortie :

2026-04-24 10:09:37 demo-localstack-bucket
2026-04-24 01:42:40 localstack-thumbnails-app-images
2026-04-24 01:42:42 localstack-thumbnails-app-resized

Envoyer et lister des objets

La commande cp prend une source et une destination. Pour copier plusieurs fichiers, il faut lancer plusieurs commandes ou utiliser --recursive pour copier un répertoire.

awslocal s3 cp function.zip s3://demo-localstack-bucket/
awslocal s3 cp code.tf s3://demo-localstack-bucket/
awslocal s3 cp output.txt s3://demo-localstack-bucket/

Pour copier tout le dossier courant :

awslocal s3 cp . s3://demo-localstack-bucket/ --recursive
# Avec des filtres
awslocal s3 cp . s3://demo-localstack-bucket/ \
  --recursive \
  --exclude "*" \
  --include "*.tf" \
  --include "*.zip"

La commande s3api put-object permet de voir plus directement l’appel API sous-jacent :

awslocal s3api put-object \
  --bucket demo-localstack-bucket \
  --key docs/code.tf \
  --body code.tf

Lister les objets :

awslocal s3 ls s3://demo-localstack-bucket/
awslocal s3api list-objects --bucket demo-localstack-bucket

Synchroniser un répertoire

sync copie récursivement les fichiers nouveaux ou modifiés entre un dossier local et S3. L’option --dryrun permet de vérifier ce qui serait copié sans modifier le bucket.

awslocal s3 sync /tmp s3://demo-localstack-bucket/tmp/ --dryrun
awslocal s3 sync /tmp s3://demo-localstack-bucket/tmp/

Lister le contenu du bucket :

awslocal s3 ls s3://demo-localstack-bucket/

Générer une URL présignée

Une URL présignée permet de donner un accès temporaire en HTTP à un objet. LocalStack permet de tester ce comportement localement.

awslocal s3 cp fichier.txt s3://demo-localstack-bucket/fichier.txt
awslocal s3 presign s3://demo-localstack-bucket/fichier.txt

La commande renvoie une URL que l’on peut tester avec curl :

curl "http://localhost:4566/demo-localstack-bucket/fichier.txt?..."

Ce qui se passe dans le conteneur LocalStack avec S3

Quand on crée un bucket S3 avec LocalStack, par exemple avec :

awslocal s3 mb s3://demo-bucket

LocalStack ne crée pas un vrai bucket dans AWS. Il reçoit l’appel API S3 sur son endpoint local, puis il enregistre cette ressource dans son état interne. Le bucket devient donc une ressource émulée connue par LocalStack. On peut ensuite la retrouver avec :

awslocal s3 ls

Le bucket est seulement un conteneur logique. Il ne correspond pas à un dossier Linux classique. Quand on envoie un fichier dans ce bucket :

awslocal s3 cp fichier.txt s3://demo-bucket/fichier.txt

la CLI AWS lit le fichier local fichier.txt, l’envoie à l’API S3 de LocalStack, puis LocalStack stocke son contenu comme un objet S3. L’objet est identifié par :

  • le nom du bucket : demo-bucket
  • la clé de l’objet : fichier.txt
  • des métadonnées : taille, type de contenu, date de modification, etag, etc.

Dans LocalStack, le stockage physique dépend de la configuration du conteneur. Par défaut, les données vivent dans l’état du conteneur LocalStack. Si le conteneur est supprimé sans volume persistant, les ressources peuvent être perdues. Avec une configuration de persistance ou un volume Docker, LocalStack peut conserver son état entre deux démarrages.


2. Amazon Elastic Compute Cloud EC2 avec LocalStack

Dans AWS, EC2 correspond à des machines virtuelles.
Dans LocalStack, EC2 est principalement une instance de simulation d’API qui imite une instance EC2. Cela signifie que l’on peut tester des appels AWS ou des fichiers Terraform, mais cela ne crée pas une vraie VM complète comme dans AWS réel.

Avec LocalStack, créer une instance EC2 ne lance généralement pas une vraie machine virtuelle mais une ressource simulée dans LocalStack. C'est utile pour apprendre Terraform et les API AWS, mais ce n'est pas équivalent à une VM réelle.

Terraform avec LocalStack

Terraform permet de décrire de l’infrastructure sous forme de code.
Avec LocalStack, on peut tester cette infrastructure localement.

L'architecture du répértoire:  
tf-ec2-localstack/
├── provider.tf
├── main.tf
└── outputs.tf   # optionnel

# Exemple de provider AWS pour LocalStack provider.tf

```hcl
provider "aws" {
  access_key                  = "test"
  secret_key                  = "test"
  region                      = "us-east-1"
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    ec2 = "http://localhost:4566"
  }
}


# Exemple d'instance EC2 simulée main.tf

```hcl
resource "aws_instance" "demo" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  tags = {
    Name = "localstack-demo-ec2"
  }
}
# Commandes Terraform locales pour Localstack
tflocal init
tflocal plan
tflocal apply

# Voir l'état Terraform
tflocal state list
tflocal show

# Voir les instances côté LocalStack
awslocal ec2 describe-instances

#Compter les instances simulées :
awslocal ec2 describe-instances \
  --query "length(Reservations[].Instances[])" \
  --output text

# Détruire les ressources
tflocal destroy

3. Lambda

Lambda est un service qui permet d’exécuter une fonction à la demande, sans gérer de serveur directement.

Une fonction Lambda fonctionne globalement ainsi :

événement → fonction handler → réponse

On déploie du code, puis on invoque la fonction.

Exemple Lambda avec un runtime en Python

mkdir -p ~/lambda-python # Créer un dossier :

cd ~/lambda-python

Créer le fichier lambda_function.py :

import json

def handler(event, context):
    body = json.loads(event["body"])

    num1 = body["num1"]
    num2 = body["num2"]
    product = num1 * num2

    return {
        "statusCode": 200,
        "body": f"The product of {num1} and {num2} is {product}"
    }

Créer le zip :

zip function.zip lambda_function.py

Créer la fonction Lambda :

awslocal lambda create-function \
  --function-name lambda-python-product \
  --runtime python3.12 \
  --zip-file fileb://function.zip \
  --handler lambda_function.handler \
  --role arn:aws:iam::000000000000:role/lambda-role

Invoquer la fonction :

awslocal lambda invoke \
  --function-name lambda-python-product \
  --payload '{"body":"{\"num1\":6,\"num2\":7}"}' \
  response.json

cat response.json

Exemple Lambda en JavaScript / Node.js

Créer index.js :

exports.handler = async (event) => {
    let body = JSON.parse(event.body);
    const product = body.num1 * body.num2;

    return {
        statusCode: 200,
        body: "The product of " + body.num1 + " and " + body.num2 + " is " + product,
    };
};

Créer le zip :

zip function.zip index.js

Créer la fonction :

awslocal lambda create-function \
  --function-name localstack-lambda-url-example \
  --runtime nodejs22.x \
  --zip-file fileb://function.zip \
  --handler index.handler \
  --role arn:aws:iam::000000000000:role/lambda-role

Exemple de fonction Lambda Word Counter en Python

On crée un bucket d’entrée pour déposer les fichiers, puis un bucket de sortie pour stocker les résultats.
L’objectif sera de déclencher la fonction lorsqu’un fichier texte sera téléversé dans le bucket d’entrée. La fonction compte le nombre de lignes et de caractéres dans le fichier puis le stocke en fichier json dans le bucket de sortie.

import boto3 # SDK pour Python pour manipuler des services AWS comme S3 et EC2.  
import json
import os
import re
from urllib.parse import unquote_plus

s3 = boto3.client("s3")
OUTPUT_BUCKET = os.environ.get("OUTPUT_BUCKET", "results-bucket")


def handler(event, context):
    print("Lambda déclenchée par S3")

    results = []

    for record in event.get("Records", []):
        source_bucket = record["s3"]["bucket"]["name"]
        key = unquote_plus(record["s3"]["object"]["key"]) # key est le nom du fichier.txt

        print(f"Lecture du fichier : s3://{source_bucket}/{key}")

        obj = s3.get_object(Bucket=source_bucket, Key=key)
        content = obj["Body"].read().decode("utf-8")

        lines = content.splitlines()
        words = re.findall(r"\b[\w'-]+\b", content)
        chars = len(content)

        result = {
            "source_bucket": source_bucket,
            "source_key": key,
            "line_count": len(lines),
            "word_count": len(words),
            "character_count": chars,
        }

        output_key = key + ".wordcount.json"

        s3.put_object(
            Bucket=OUTPUT_BUCKET,
            Key=output_key,
            Body=json.dumps(result, indent=2, ensure_ascii=False).encode("utf-8"),
            ContentType="application/json"
        )

        print(f"Rapport créé : s3://{OUTPUT_BUCKET}/{output_key}")
        print(json.dumps(result, indent=2, ensure_ascii=False))

        results.append(result)

    return {
        "ok": True,
        "processed": len(results),
        "results": results
    }

En suite, on rée l’archive ZIP de la fonction :

zip -r function.zip lambda_function.py

On crée la fonction Lambda dans LocalStack

awslocal lambda create-function \
  --function-name word-counter \
  --runtime python3.12 \
  --handler lambda_function.handler \
  --zip-file fileb://function.zip \
  --role arn:aws:iam::000000000000:role/lambda-role \
  --environment Variables="{OUTPUT_BUCKET=results-bucket}"

On peut vérifier que la fonction existe :

awslocal lambda list-functions

Tester manuellement l’invocation de la fonction :

awslocal lambda invoke \
  --function-name word-counter \
  --payload '{"Records":[]}' \
  response.json

cat response.json

A ce stade, il faut autoriser S3 à invoquer la fonction Lambda pour que S3 puisse déclencher la fonction, il faut donc ajouter une permission d’invocation :

awslocal lambda add-permission \
  --function-name word-counter \
  --statement-id s3invoke \
  --action lambda:InvokeFunction \
  --principal s3.amazonaws.com \
  --source-arn arn:aws:s3:::demo-bucket

On note bien qu’on a ajouté l’autorisation pour le bucket demo-bucket.
On configure le bucket demo-bucket pour déclencher la fonction Lambda à chaque création d’objet :

awslocal s3api put-bucket-notification-configuration \
  --bucket demo-bucket \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [
      {
        "LambdaFunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:word-counter",
        "Events": ["s3:ObjectCreated:*"]
      }
    ]
  }'

On vérifie la configuration du déclencheur :

awslocal s3api get-bucket-notification-configuration \
  --bucket demo-bucket

On crée un fichier texte de test avec le contenu suivant:

Bonjour IM2AG.
Ceci est un test avec S3 et Lambda.
La fonction compte les mots, les lignes et les caractères.

Envoyer le fichier dans le bucket S3 d’entrée :

awslocal s3 cp texte.txt s3://demo-bucket/texte.txt

À ce moment, S3 émet un événement ObjectCreated, et la fonction Lambda word-counter est déclenchée automatiquement.

On vérifie le résultat:

awslocal s3 ls s3://results-bucket
awslocal s3 cp s3://results-bucket/texte.txt.wordcount.json .
cat texte.txt.wordcount.json

Exemple de résultat attendu :

{
  "source_bucket": "demo-bucket",
  "source_key": "texte.txt",
  "line_count": 3,
  "word_count": 20,
  "character_count": 110,
  "preview": "Bonjour IM2AG.\nCeci est un test avec S3 et Lambda.\nLa fonction compte les mots, les lignes et les caractères.\n"
}

L’ensemble des services émulés créent leur ressources dans le repertoire /tmp dans le conteneur.
On peut trouver notre fonction lambda dans /tmp/lambda/awslambda-us-east-1-tasks/word-counter-b1596488-6103-46f1-8eb7-0f613232d082/code/lambda_function.py


Résumé et remarques

LocalStack permet de découvrir les principaux concepts du cloud AWS sans créer de ressources réelles sur AWS.
L’outil reproduit localement de nombreux services et constitue un environnement simple de tests pour les travaux pratiques, les démonstrations en cours et l’apprentissage de l’Infrastructure as Code avec Terraform ou OpenTofu.
Bien que LocalStack soit principalement utilisé pour l’apprentissage de l’écosystème AWS, l’outil propose également des fonctionnalités permettant de simuler certains services d’autres fournisseurs cloud comme Azure et Snowflake.