Architecturer PHP sur AWS : du MVP au scale
Pourquoi AWS reste le choix par défaut en 2026
AWS reste la plateforme cloud par défaut sur laquelle nous déployons les applications PHP de nos clients entreprise. Trois raisons, que nous revalidons chaque année.
- Breadth. Plus de 240 services, depuis l'EC2 classique jusqu'à Aurora Serverless v2 et Bedrock pour les modèles fondation. Ce que vous voulez construire existe en service managé.
- Conformité. SOC 2, ISO 27001, HDS, PCI-DSS, FedRAMP. Les entreprises réglementées (banque, santé, assurance) trouvent les certifications qu'il leur faut.
- Écosystème. Terraform, OpenTofu, CDK, Pulumi, l'outillage IaC est mature. Les talents AWS sont trois à cinq fois plus nombreux que les talents GCP ou Azure sur le marché français.
Les contreparties sont bien réelles. Les factures AWS savent surprendre (NAT Gateway, egress cross-AZ, S3 request costs). La complexité des VPC et de l'IAM est un coût d'entrée qu'il faut assumer. Et sur certains workloads simples, un Scaleway ou un OVHcloud managé font le travail pour moitié moins cher.
Cet article décrit comment nous architecturons une application PHP sur AWS selon trois paliers de maturité : MVP, Growth, Scale. Avec les services concrets, les coûts réels, le code qui va avec.
Palier 1, MVP (0 à 10 000 utilisateurs)
Objectif : aller en production vite, sans sur-ingénierie. Une instance EC2 suffit, le multi-AZ est un sur-coût injustifié à ce stade. On garde néanmoins une discipline IaC et une base solide pour évoluer.
Utilisateurs
│
▼
┌──────────┐ ┌──────────────┐
│ Route53 │─────▶│ CloudFront │
└──────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ EC2 t3.medium│
│ Nginx+PHP-FPM│
└──────┬────────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ RDS PG │ │ S3 │
│ db.t4g.micro│ │ uploads │
└──────────┘ └──────────┘
- Compute : 1 instance EC2
t3.medium(out4g.mediumGraviton pour -20 %). Nginx + PHP-FPM 8.3, Supervisor pour les workers. - Base de données : RDS PostgreSQL
db.t4g.microoudb.t4g.small. Backups automatiques 7 jours. - CDN et TLS : CloudFront devant le site, ACM pour le certificat, Route53 pour le DNS.
- Assets : S3 pour les uploads utilisateurs, servis via CloudFront.
- Emails : SES pour transactionnel, SNS pour alertes internes.
- Monitoring : CloudWatch Logs + 3 alarmes (CPU, erreurs HTTP 5xx, disque).
Coût mensuel typique à ce palier : 80 à 150 €, hors SES (variable selon volume).
Le piège classique à éviter : ne pas se jeter sur RDS Multi-AZ (+100 % sur la facture DB) tant que le SLO ne l'exige pas. Un snapshot quotidien + un script de restore testé suffisent pour une app B2B en MVP.
Palier 2, Growth (10 000 à 500 000 utilisateurs)
On encaisse de la charge, des pics, et la disponibilité devient un vrai engagement. Multi-AZ partout sur les services stateful, horizontalité sur le compute, session Redis pour dégager PHP-FPM de l'état.
Utilisateurs
│
▼
┌──────────────┐
│ CloudFront │
└──────┬───────┘
│
▼
┌──────────────┐
│ ALB │
└──────┬───────┘
│
Auto Scaling Group
┌─────┴─────┬─────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ EC2-A │ │ EC2-B │ │ EC2-C │
└───┬───┘ └───┬───┘ └───┬───┘
└─────┬─────┴─────────┘
│
┌───────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌──────┐
│RDS MAZ │ │ElastiCache│ │ S3 │
│PostgreSQL│ │ Redis │ │ │
└─────────┘ └───────────┘ └──────┘
- Compute : ALB + Auto Scaling Group avec 2 à 6 instances EC2
m6i.large(oum7g.largeGraviton). Deux zones de disponibilité minimum. - Base de données : RDS PostgreSQL Multi-AZ,
db.m6g.largeavec 100 Go gp3 et IOPS provisionnés si nécessaire. - Cache et session : ElastiCache Redis 7, mode Cluster, deux nœuds, session PHP déportée (
php.ini: session.save_handler = redis). - Assets et uploads : S3 + CloudFront.
- Queue : SQS pour les jobs asynchrones (envoi d'emails, webhooks, traitements en arrière-plan).
- Crons : EventBridge Scheduler déclenchant des Lambdas ou des tâches ECS à la demande.
- Observabilité : CloudWatch Logs Insights, CloudWatch Metrics, X-Ray pour le tracing, dashboards et alarmes SLO.
Coût mensuel typique : 400 à 800 €. Les gros postes sont l'ASG EC2, RDS Multi-AZ et le trafic CloudFront.
À ce palier, nous containerisons systématiquement l'application, même si elle tourne encore en AMI. Le Dockerfile devient l'interface de déploiement, ce qui prépare le saut vers Fargate au palier suivant.
Palier 3, Scale (500 000 utilisateurs et plus)
Multi-région de lecture ou disaster recovery, services managés partout, auto-scaling fin, observabilité premier plan.
- Compute : ECS Fargate (ou EKS si l'équipe est kube-native) avec service auto-scaling basé sur les métriques ALB et CloudWatch.
- Base de données : Aurora PostgreSQL Serverless v2, read replicas multi-AZ, Aurora Global Database si cross-région nécessaire.
- Cache : ElastiCache Redis cluster mode enabled, 3 shards minimum, réplication cross-AZ.
- Recherche : OpenSearch managé ou OpenSearch Serverless.
- Asynchrone : SQS + Lambda pour traitements courts, ECS tasks pour traitements longs.
- Batch : AWS Batch ou ECS scheduled tasks, parfois Step Functions pour orchestrer.
- CDN : CloudFront avec origin shield, Lambda@Edge pour les règles de routage ou d'A/B testing.
- WAF : AWS WAF devant CloudFront et l'ALB, règles managées OWASP + règles bot control.
- Observabilité : Prometheus et Grafana sur EKS pour les équipes kube, ou Managed Grafana + Managed Prometheus, Datadog ou New Relic selon choix ops.
Coût mensuel typique : 2 000 à 10 000 € selon volumétrie. Sur un client traitant 20 M de requêtes par jour, nous sommes à 6 500 € par mois en régime stable.
Dockerfile PHP-FPM production-ready
Le conteneur PHP est le socle du déploiement. Voici la base que nous utilisons et durcissons projet par projet.
# syntax=docker/dockerfile:1.7
FROM php:8.3-fpm-alpine AS base
RUN apk add --no-cache \
icu-libs \
libpq \
libzip \
oniguruma \
tzdata \
&& docker-php-ext-install -j$(nproc) \
bcmath \
intl \
opcache \
pcntl \
pdo_pgsql \
zip \
&& pecl install redis apcu \
&& docker-php-ext-enable redis apcu
RUN { \
echo 'opcache.enable=1'; \
echo 'opcache.enable_cli=0'; \
echo 'opcache.memory_consumption=256'; \
echo 'opcache.interned_strings_buffer=32'; \
echo 'opcache.max_accelerated_files=20000'; \
echo 'opcache.validate_timestamps=0'; \
echo 'opcache.preload=/app/config/preload.php'; \
echo 'opcache.preload_user=www-data'; \
echo 'realpath_cache_size=4096k'; \
echo 'realpath_cache_ttl=600'; \
} > /usr/local/etc/php/conf.d/99-opcache-prod.ini
COPY --from=composer:2.8 /usr/bin/composer /usr/bin/composer
FROM base AS deps
WORKDIR /app
COPY composer.json composer.lock symfony.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --no-progress --no-interaction
FROM base AS runtime
WORKDIR /app
COPY --from=deps /app/vendor ./vendor
COPY . .
RUN composer dump-autoload --optimize --classmap-authoritative --no-dev \
&& php bin/console cache:warmup --env=prod --no-debug \
&& chown -R www-data:www-data var public
USER www-data
EXPOSE 9000
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s \
CMD SCRIPT_NAME=/health SCRIPT_FILENAME=/health REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1
CMD ["php-fpm", "-F"]
Trois choses non négociables dans nos Dockerfiles de prod :
- Multi-stage pour ne pas embarquer Composer et les devDependencies dans l'image finale.
- OPcache durci avec
validate_timestamps=0et preload Symfony. Gain de latence systématique de 10 à 30 %. - User non-root (
www-data). Nous refusons les images qui tournent en root.
Sur ECS Fargate, ce conteneur tourne dans une task avec un sidecar Nginx (nginx:1.27-alpine) partageant un volume éphémère sur lequel Symfony a copié public/.
Module Terraform pour un service Fargate PHP
Nous maintenons un module Terraform réutilisable pour déployer un service PHP sur ECS Fargate avec ALB, service auto-scaling et logs. Voici un extrait représentatif.
module "php_service" {
source = "./modules/fargate-php"
name = "invoicing-api"
cluster_arn = aws_ecs_cluster.main.arn
vpc_id = module.vpc.vpc_id
private_subnets = module.vpc.private_subnets
public_subnets = module.vpc.public_subnets
container_image = "${aws_ecr_repository.app.repository_url}:${var.image_tag}"
cpu = 1024
memory = 2048
desired_count = 3
min_capacity = 3
max_capacity = 20
target_cpu = 60
target_alb_rps = 400
environment = {
APP_ENV = "prod"
APP_DEBUG = "0"
}
secrets = {
APP_SECRET = aws_secretsmanager_secret.app_secret.arn
DATABASE_URL = aws_secretsmanager_secret.database_url.arn
REDIS_URL = aws_secretsmanager_secret.redis_url.arn
ANTHROPIC_API_KEY = aws_secretsmanager_secret.anthropic.arn
}
log_retention_days = 30
enable_xray = true
alb_health_path = "/health"
tags = {
Project = "invoicing"
Environment = "prod"
ManagedBy = "terraform"
}
}
Le module encapsule un paquet de best practices :
- Task role IAM au périmètre minimal.
- Secrets tirés depuis Secrets Manager, jamais en clair dans la task definition.
- Service discovery via Cloud Map si plusieurs services communiquent en interne.
- Auto-scaling cible par CPU ET par RPS ALB, avec cooldown à 60 secondes.
- Sidecar X-Ray activé si
enable_xray = true. - Log group dédié par service, rétention configurable.
Spécificités PHP sur AWS
- Sessions : déporter via Redis dès qu'il y a plus d'une instance.
session.save_handler = redis,session.save_path = tcp://redis:6379. - File uploads : ne jamais écrire sur le disque de la task. Uploads directs S3 (presigned POST) ou upload côté app puis
putObjectvers S3 et purge du tmp. - Logs applicatifs : Monolog en JSON stdout, CloudWatch Logs capte la sortie du conteneur, Logs Insights pour les recherches.
- Crons : EventBridge Scheduler + tâche ECS
run-task. Ne jamais laisser un cron crontab dans un conteneur, ça ne survit pas à l'auto-scaling. - Workers : un service ECS séparé consomme SQS (ex.
bin/console messenger:consume). - Warmup :
cache:warmupà la construction de l'image. Sur Fargate, chaque cold start pendant le scale-up coûte sans cette étape.
Sécurité AWS
Les règles minimales que nous ne négocions pas sur un workload PHP entreprise.
- VPC avec subnets privés pour les tasks et les DB, subnets publics uniquement pour ALB et NAT Gateway.
- Security groups au périmètre strict, pas de
0.0.0.0/0inbound sur les tasks. - IAM least privilege : chaque task role a ses actions listées une à une, pas de
*. - Secrets Manager pour les secrets dynamiques, KMS pour les clés de chiffrement.
- RDS chiffré au repos (KMS), TLS en transit imposé côté driver.
- CloudTrail activé sur tous les comptes, logs centralisés dans un compte audit.
- GuardDuty activé, findings remontés vers Security Hub ou un SIEM externe.
- AWS WAF devant CloudFront et l'ALB en prod.
Observabilité
Les quatre piliers pour un PHP en prod sur AWS :
- Logs : Monolog JSON → CloudWatch Logs → Logs Insights pour ad-hoc, export S3 pour long terme.
- Métriques : CloudWatch Metrics, custom metrics via PutMetricData ou EMF (embedded metric format). Dashboards par domaine métier.
- Traces : X-Ray SDK PHP pour tracer les appels aux DB, au cache, aux APIs externes. Ou OpenTelemetry si vous voulez rester neutre.
- Alerting : CloudWatch Alarms → SNS → Slack ou PagerDuty. Alertes basées SLO, pas sur la CPU brute.
Coûts réels et comment les réduire
Sur 20 missions AWS, les leviers qui comptent vraiment :
- Graviton (ARM) : -20 à -40 % sur EC2, RDS, ElastiCache par rapport à Intel équivalent. La majorité des images PHP 8.3 tournent nativement en ARM depuis longtemps.
- Savings Plans compute 3 ans no-upfront : -40 % environ sur le compute si la charge est stable.
- Reserved Instances RDS 1 an no-upfront : -30 à -40 % sur la DB.
- S3 Intelligent-Tiering pour les assets peu consultés.
- CloudFront caching correct : le hit ratio doit être > 85 %, sinon on paie du transfer out EC2.
- VPC Endpoints pour S3, DynamoDB, Secrets Manager : évite les NAT Gateway charges sur le traffic AWS interne.
Pièges classiques que nous débusquons en audit
- NAT Gateway facturé sans le voir. 0,045 $ par Go processé + 0,045 $ par heure. Un workload qui parle beaucoup à Internet peut payer 500 à 1 500 € par mois juste sur le NAT.
- RDS sur-dimensionné. Des instances
db.r6g.xlargetournant à 8 % de CPU. Revue trimestrielle obligatoire. - Transfer out cross-AZ. Souvent oublié. Un ALB qui balance sur 3 AZ avec des backends tout en cross-AZ traffic peut ajouter 10 à 15 % à la facture.
- ALB idle. Plusieurs ALB "au cas où". Chacun coûte ~20 € par mois plus les LCUs. Consolider.
- CloudWatch Logs en rétention infinie. 5 à 10 ans de logs sur Splunk interne. Rétention 30 jours + archive S3 suffit la plupart du temps.
- EBS gp2. Toujours migrer vers gp3 (moins cher, meilleures perfs baseline).
Alternatives où AWS n'est pas le meilleur choix
- Scaleway, OVHcloud pour un SaaS mono-région à budget serré : tarifs 2 à 3 fois plus bas sur le compute, souveraineté française, support en français.
- GCP si vous utilisez déjà BigQuery ou Vertex AI. Cloud Run pour PHP containerisé est excellent.
- Azure dans les environnements Microsoft-heavy (Active Directory, Office 365, Dynamics). L'intégration AAD et les licences liées peuvent justifier Azure même pour un PHP.
- Platform.sh ou Clever Cloud pour les équipes qui ne veulent pas gérer d'infra du tout, sur des applications simples.
Conclusion
AWS est un excellent choix par défaut pour une application PHP d'entreprise en 2026, à condition de traiter les coûts avec la même rigueur que le code. Les trois paliers MVP, Growth, Scale structurent la trajectoire : on commence simple, on ajoute du managé au fur et à mesure de la croissance, on gagne en résilience et en élasticité sans sur-ingénierie prématurée.
Pour un cadrage AWS sur votre workload PHP, une migration on-premise vers AWS ou un audit de facture, écrivez-nous à contact@your-digital-hub.com ou découvrez nos prestations hébergement et DevOps.