Scaling
Scale a Temps app by running more replicas per environment, raising its CPU and memory limits, or moving to a bigger server. In the dashboard CPU is set in cores (0.5, 1, 2) and memory in MB; the API takes CPU in microcores (1_000_000 = 1 core). Only limits are applied to containers, and changes take effect on the next deployment.
Temps does not auto-scale. You set the replica count and resource limits yourself, per environment, and adjust them as traffic changes.
Scaling options
Vertical scaling (bigger server)
- More CPU cores and RAM
- Simplest change — upgrade your VPS
- No application changes needed
- Best for database-heavy workloads
Horizontal scaling (more replicas)
- Multiple container instances per environment
- Built-in to Temps via replica configuration
- Requires stateless application design
- Best for CPU-bound web traffic
Multi-node scaling — if you need more capacity than a single server provides, you can add worker nodes to distribute containers across multiple machines; see that guide for how the scheduler picks which node runs a given replica.
Before scaling, check whether optimization can solve the problem for free. A slow database query or missing index can make an application feel overloaded when the server has plenty of capacity.
When to scale
| Symptom | Likely cause | First action |
|---|---|---|
| CPU consistently above 80% | Application is compute-bound | Raise the CPU limit or add replicas |
| Response times increasing gradually | CPU or memory pressure | Check resource usage (below) |
| Slow responses or timeouts under load | Too few replicas, or CPU saturation | Add replicas, or optimize queries |
| Memory approaching the limit | Large in-memory datasets, or a leak | Raise the memory limit; investigate if usage keeps growing |
| Container restarts (OOM) | Memory limit too low | Raise the memory limit |
| High response times, low CPU | I/O-bound: waiting on the database | Optimize queries, add indexes, add Redis caching, use connection pooling |
| Build times increasing | Build-time CPU/memory | Upgrade the server for faster builds |
Check current resource usage
Before changing anything, check what the application actually uses. In the dashboard, open the project, go to Environments, pick the environment and open a container. The container view streams live CPU, memory and network usage.
The same numbers are available from the API:
curl "https://your-temps-instance/api/projects/{project_id}/environments/{env_id}/containers/{container_id}/metrics" \
-H "Authorization: Bearer YOUR_TOKEN"
{
"container_id": "…",
"container_name": "…",
"cpu_percent": 45.2,
"cpu_limit_cores": 1.0,
"memory_bytes": 536870912,
"memory_limit_bytes": 1073741824,
"memory_percent": 50.0,
"network_rx_bytes": 1048576,
"network_tx_bytes": 524288,
"timestamp": "…"
}
cpu_percent follows the Docker convention: 200 means two cores fully used. cpu_limit_cores, memory_limit_bytes and memory_percent are null when no limit is set. Add /history to the path for past samples, or /stream for a Server-Sent Events feed.
Replicas
Temps can run several container instances (replicas) for each environment. The proxy distributes requests across all healthy replicas.
Add replicas
Scale replica count
- 1
Go to your environment's Settings.
- 2
Set Replicas to the desired count (e.g. 3).
- 3
Save and redeploy to roll out the new replica count.
Checkpoint: Open the environment's containers and confirm the expected number of replicas are running.
You can set the replica count at two levels:
- Project (default for every environment): project > Settings > Build & deploy > Deploy tab > Default Replicas.
- Environment (overrides the project): project > Environments > select environment > Configuration > Replicas.
The environment setting takes priority over the project setting. Save, then redeploy to apply the new count.
How replicas work
When you deploy with multiple replicas:
- Temps creates N containers (e.g.
myapp-1,myapp-2,myapp-3) - Each container gets its own host port
- All containers run the same image with the same environment variables
- The proxy distributes incoming requests across all healthy replicas
- Health checks run independently per replica
- If any replica fails during deployment, all replicas are cleaned up and the deployment fails
Replicas share the image, environment variables, linked managed services (database, Redis), domain and certificate. They do not share CPU and memory (each replica gets its own limits), the local filesystem or in-memory caches.
Requirements for replicas
Your application must be stateless — it cannot rely on local files, in-memory sessions, or container-local state that is not shared:
- Sessions: Store in Redis or a database, not in memory
- File uploads: Store in S3 or blob storage, not the local filesystem
- Cache: Use Redis or an external cache, not in-process memory
- WebSocket connections: Each client connects to one replica — use a pub/sub system (Redis) if clients need to communicate across replicas
Stateless session example
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
Resource limits
Each container can be given CPU and memory limits. New projects start with a conservative default profile rather than being fully uncapped — a hard memory limit, so a runaway app can't OOM a small single-node host, plus recorded CPU and memory request values. You can raise, lower, or uncap these per environment at any time.
| Setting | Dashboard unit | API unit | Default | Applied to the container? |
|---|---|---|---|---|
| CPU request | cores | microcores | 0.5 CPU | No — recorded only |
| CPU limit | cores | microcores | Unset (uncapped) | Yes |
| Memory request | MB | MB | 128 MB | No — recorded only |
| Memory limit | MB | MB | 512 MB | Yes; 0 = uncapped |
Only the limits are applied to the container. The request values are stored and shown in the dashboard and CLI, but Temps doesn't currently pass them to Docker as reservations or use them when choosing a node, so they don't guarantee any minimum CPU or memory. On a shared host, other workloads can still contend for those resources.
Adjust CPU and memory limits
Set CPU and memory limits
- 1
Open your project in the dashboard and click Environments in the sidebar.
- 2
Click on the environment you want to scale (e.g. production) and go to Settings.
- 3
Set CPU Limit in cores (e.g. 0.5, 1, 2) and Memory Limit in MB. Leave CPU Limit empty for no limit; set Memory Limit to 0 (Uncapped) to remove the memory cap.
- 4
Click Save. Changes take effect on the next deployment; click Redeploy on the current deployment to apply immediately.
Checkpoint: After redeploying, open the environment's container and confirm CPU and memory usage stay under the new limits.
In the dashboard (project > Environments > select environment > Settings):
- CPU Limit (cores) — the most CPU the container can use, in cores (
0.5,1,2). The dashboard converts cores to microcores for you. The container is throttled above this. Leave it empty for no limit. - Memory Limit (MB) — the most memory the container can use. Above it, Docker kills the container and Temps restarts it. Leave it empty to inherit the project default, or set
0(Uncapped) to run with no memory limit. Swap is capped at the same value, so the limit is a real hard cap. - CPU Request and Memory Request — recorded only, as described above.
Click Save. Changes take effect on the next deployment; to apply them now, click Redeploy on the current deployment.
CLI unit bug
The CLI's environments resources command has a known unit bug in its --cpu/--cpu-request flags: they're documented and displayed as millicores (1000 = 1 core), but the value is passed straight through to a field the API stores in microcores (1_000_000 = 1 core), with no conversion. Setting --cpu 1000 therefore asks for a 0.001-core limit — too small for Docker to apply, so the container fails to start and the deployment fails. Until this is fixed, pass the raw microcore value instead of the number the flag's help text suggests:
# Sets a 1 CPU core limit (1_000_000 microcores) and a 0.5 CPU core request (500_000 microcores)
bunx @temps-sdk/cli environments resources production -p my-project \
--cpu 1000000 --cpu-request 500000 --memory 1024 --memory-request 128
- Name
--cpu- Type
- microcores, despite the flag's own help text saying millicores
- Description
Maximum CPU allocation.
1_000_000= 1 full CPU core,500_000= half a core.
- Name
--memory- Type
- MB
- Description
Maximum memory allocation in megabytes, e.g.
512.
- Name
--cpu-request- Type
- microcores, despite the flag's own help text saying millicores
- Description
CPU request. Recorded only — not currently enforced or used for scheduling.
- Name
--memory-request- Type
- MB
- Description
Memory request. Recorded only — not currently enforced or used for scheduling.
If you set a memory limit and your container exceeds it, Docker kills it and Temps restarts it (restart policy is always). If this happens repeatedly, raise the memory limit or investigate memory leaks. A CPU limit is not set by default, so a CPU-bound container can use all available cores unless you cap it explicitly.
For the full list of per-environment fields, see the resource settings reference.
Scale via the API
Update an environment's settings with a PUT. CPU values are in microcores and memory in MB:
curl -X PUT "https://your-temps-instance/api/projects/{project_id}/environments/{env_id}/settings" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"cpu_limit": 2000000,
"memory_limit": 1024,
"replicas": 3
}'
"cpu_limit": 2000000 is two cores. Fields you leave out are unchanged; send null for cpu_limit or memory_limit to clear it. Trigger a redeploy for the changes to take effect.
Change the exposed port
If your app listens on a port other than 3000, set Exposed Port in the environment's settings (or the project's default port) and redeploy. How that setting interacts with an EXPOSE line in your image depends on the Temps version — see Networking: Ports.
Vertical scaling
Upgrade your VPS to get more CPU and RAM. This gives more resources to all containers.
Steps
- Check current usage — Look at CPU and memory utilization in the Temps dashboard or via
htopon the server - Upgrade the VPS — Use your provider's upgrade option (DigitalOcean, Hetzner, Linode, AWS, etc.)
- Temps continues running — Most VPS upgrades preserve the disk. Temps and your containers restart automatically after the server reboots
Recommended server sizes
| Traffic level | Server spec |
|---|---|
| Hobby / side project | 2 CPU, 4 GB RAM |
| Small production app | 4 CPU, 8 GB RAM |
| Medium traffic | 8 CPU, 16 GB RAM |
| High traffic | 16+ CPU, 32+ GB RAM |
These are rough guidelines. Actual needs depend on your application — a database-heavy app needs more RAM; a compute-heavy app needs more CPU. For providers, specific plans and current prices, see Choosing a server.
Optimize before scaling
Optimization is free and often more effective than adding resources. For caching, CDN and query tuning in depth, see Performance.
Database queries
The most common performance bottleneck. Check for:
- N+1 queries — Loading related data in a loop instead of a JOIN
- Missing indexes — Add indexes on columns used in WHERE, JOIN, and ORDER BY clauses
- Expensive queries — Use
EXPLAIN ANALYZEto find slow queries - Connection pooling — Set appropriate pool sizes (20-50 connections per container), or put PgBouncer in front of PostgreSQL
All replicas share the same database, so a database bottleneck is not solved by adding replicas.
Caching
Add caching for data that does not change on every request:
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
async function getUsers() {
const cached = await redis.get('users');
if (cached) return JSON.parse(cached);
const users = await db.query('SELECT * FROM users');
await redis.set('users', JSON.stringify(users), { EX: 300 }); // 5 min cache
return users;
}
Frontend performance
For static sites and SPAs deployed on Temps:
- Temps already applies gzip compression, cache headers, and ETags automatically
- Use code splitting to reduce initial bundle size
- Lazy load routes and heavy components
- Optimize images (use modern formats like WebP/AVIF)
Application profiling
Profile your application to find hotspots before guessing:
- Node.js:
node --inspectorclinic.js - Python:
cProfileorpy-spy - Go:
pprof - Rust:
flamegraph
Monitoring
Track these metrics to know when scaling is needed:
- Response time — Increasing trend means the server is under pressure
- CPU usage — Sustained >80% means CPU-bound; add replicas or upgrade
- Memory usage — Approaching the limit means containers may get killed
- Error rate — Spikes correlate with resource exhaustion
Temps includes built-in monitoring and analytics. For server-level metrics, use standard tools:
# Real-time resource usage
htop
# Container-level stats
docker stats
# Disk usage
df -h
For application-level monitoring, add OpenTelemetry tracing — Temps injects OTEL_* environment variables automatically. See the observability tutorial for setup instructions.
Troubleshooting
Container keeps restarting
The container is hitting its memory limit and being killed by Docker. Check the container logs for OOM (out of memory) errors. Increase the memory limit or fix memory leaks in your application.
Deployment fails right after changing CPU
Check the unit. A CPU value entered as millicores (1000) through the CLI or API is read as microcores — 0.001 of a core — which Docker cannot apply, so the container fails to start. Use cores in the dashboard, or microcores (1000000 = 1 core) in the CLI and API.
Adding replicas does not help
Not all performance problems are solved by more replicas:
- Database bottleneck — All replicas share the same database. More replicas means more database connections but the same query performance.
- External API rate limits — More replicas make more requests to external APIs. You may hit rate limits faster.
- Single-threaded applications — Node.js runs on a single thread. Each replica uses one CPU core. If your server has 4 cores, 4 replicas fully utilize the CPU. Beyond that, you need a bigger server.
Deployment is slow
Build time is separate from runtime performance. If builds are slow:
- Use multi-stage Docker builds to cache dependency installation
- Use
.dockerignoreto reduce build context size - Upgrade the server for faster builds (builds use half of available CPU and memory)