Architecture Note: Deploying Stateless Web Applications on Scalingo
An architecture decision note for running stateless web apps on Scalingo: smallest viable design, trust boundaries around build-time secrets and addon credentials, operational checks for deploys and scaling, failure modes with mitigations, and conditions that would force a redesign.
11 Dec 2025, 20:34 UTC

The Problem: Choosing a PaaS That Keeps Operations Simple
Teams moving from raw VMs or Kubernetes often want a platform that handles builds, scaling, and secrets without requiring a dedicated platform team. Scalingo fits this niche: you push code via Git, the platform builds an image using buildpacks or a Dockerfile, and you scale process types independently. The trade-off is accepting ephemeral containers, multi-tenant network boundaries, and addon-managed credentials. This note captures the smallest viable architecture for a stateless web service on Scalingo, the trust boundaries you inherit, and the operational checks that catch drift before users do.
Requirements and Scope
- Stateless HTTP service (API, web app, or worker) that can run in a single container image
- Zero-downtime deploys via horizontal scaling
- Managed PostgreSQL and Redis for persistence and caching
- Secrets never committed to source control
- Team can operate with CLI and dashboard; no custom infrastructure code
Out of scope: stateful workloads, custom VPC peering, compliance regimes requiring dedicated hardware, or multi-region active-active deployments.
Smallest Suitable Design
Repository Layout
my-app/
├── Procfile # declares process types
├── .buildpacks # optional: pins buildpack URLs
├── package.json # or requirements.txt, go.mod, etc.
├── src/
└── .scalingo/ # optional: review apps, environment config
Procfile
web: gunicorn -w 4 -b 0.0.0.0:$PORT myapp.wsgi
worker: celery -A myapp worker -l info
release: python manage.py migrate --noinput
The web process receives HTTP traffic on the $PORT environment variable injected by the platform. The worker type runs background jobs and scales independently. The release command runs once per successful build before new containers replace old ones—useful for database migrations.
Pinning the Buildpack
Scalingo stacks (e.g., scalingo-22, scalingo-24) update buildpack defaults. To avoid surprise runtime upgrades on redeploy, create a .buildpacks file at the repo root:
https://github.com/scalingo/python-buildpack#v1.8.2
https://github.com/scalingo/nodejs-buildpack#v2.1.0
If you prefer full control, provide a Dockerfile instead; Scalingo will build that image and ignore buildpacks.
Trust and Data Boundaries
Build-Time vs. Runtime Secrets
Buildpacks execute on platform-managed builders. Any secret present in the repository (API keys in .env, tokens in package.json) becomes part of the build image and may be logged. Never embed secrets in source. Instead:
- Add secrets via the dashboard or CLI after the first deploy:
scalingo --app my-app env-set DATABASE_URL=postgres://... - Use addon-provided environment variables (see below).
- For build-time only secrets (private npm registry token), use the
SCALINGO_BUILD_ENV_prefix; they are stripped from the final image.
Addon-Managed Credentials
Provisioning a PostgreSQL addon injects DATABASE_URL and SCALINGO_POSTGRESQL_* variables into the application environment. The platform rotates credentials on addon plan changes or manual rotation, but the running containers only pick up new values after a restart or redeploy. Plan for this by:
- Configuring your ORM/client to read the URL at startup, not at module import time.
- Running
scalingo --app my-app restartafter credential rotation.
Network and Storage
- Containers share a private network per app; addons are reachable via their injected hostnames.
- No persistent local filesystem: uploads, compiled assets, or SQLite files disappear on restart, scale, or redeploy. Use S3-compatible object storage (Scalingo Object Storage or external) and managed databases.
- Multi-tenancy: network isolation, backup retention, and data residency depend on region (e.g.,
osc-fr1,osc-secnum-fr1) and plan. Verify compliance requirements before selecting a region.
Operational Checks
Deploy Verification
- Create the app and push:
scalingo create my-app --region osc-fr1
git push scalingo main - Watch the build log for buildpack detection, dependency installation, and
releasecommand success. - Confirm the
webprocess showsrunninginscalingo --app my-app ps.
Scaling Verification
# Scale web to 3 containers with 512 MB each
scalingo --app my-app scale web:3:512M
# Verify
dashboard.scalingo.com/apps/my-app # shows 3/3 containers, 512 MB each
scalingo --app my-app ps # lists three web containers
Expected check: dashboard and CLI report the same container count and memory. Risk: scaling beyond plan limits fails silently in the CLI; the dashboard shows an error banner.
Addon Connectivity Test
# Provision PostgreSQL
scalingo --app my-app addon-add scalingo-postgresql:starter
# Verify env var exists
scalingo --app my-app env | grep DATABASE_URL
# Test from a one-off container
scalingo --app my-app run --size M 'python -c "import os, psycopg2; conn=psycopg2.connect(os.getenv(\"DATABASE_URL\")); print(\"OK\")"'
Run the one-off container in the same region; it inherits the app's network and environment. If the connection times out, check the addon status in the dashboard—provisioning can take several minutes.
Failure Modes and Mitigations
| Failure Mode | Symptom | Detection | Mitigation |
|---|---|---|---|
| Buildpack version drift | Runtime version changes after redeploy without code change | Deploy logs show different Python/Node version | Pin buildpacks in .buildpacks or switch to Dockerfile |
| Addon credential rotation without restart | Database connection errors after plan change | Logs show authentication failure; env var updated but pods old | Automate restart on rotation via webhook or runbook |
| Ephemeral storage loss | Uploaded files missing after scale/deploy | User reports 404 on assets; disk usage resets | Move uploads to object storage; use release for asset compilation |
| Region-level outage | All apps in region unreachable | Dashboard status page; health checks fail | Multi-region DNS failover (manual); not automatic on Scalingo |
| Plan limit exceeded | Scale command accepted but containers not created | Dashboard shows pending/error state; CLI returns success | Monitor scalingo ps after scale; set alerts on container count |
Conditions That Would Change This Design
- Stateful workloads required: move to Kubernetes or VMs; Scalingo's ephemeral model is a hard constraint.
- Compliance demands dedicated network: evaluate Scalingo's SecNumCloud region (
osc-secnum-fr1) or a different provider. - Team needs GitOps with ArgoCD/Flux: Scalingo's Git push model conflicts; consider a CI/CD pipeline that pushes to Scalingo as a final step.
- Custom build steps (native extensions, multi-stage): switch to a
Dockerfilefor full control over the build environment. - Traffic exceeds single-region capacity: design for multi-region with external load balancer and replicated addons; Scalingo does not provide global load balancing.
Practical Verification Checklist
Before promoting this architecture to production, run through the current Scalingo documentation and pricing page to confirm:
- Stack version (
scalingo-24as of late 2024) and its default buildpacks - Addon plans available in your chosen region
- Container memory/CPU limits per plan tier
- Backup retention and point-in-time recovery options for PostgreSQL/Redis
- SLA terms for the selected plan
Then execute the deploy, scale, and addon connectivity steps above in a staging app. Document any deviations from this note as team-specific runbooks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.