Deploy Docker Containers
Add a Dockerfile to your repository and Temps builds it, health-checks the new container and switches traffic to it only once it passes, behind automatic HTTPS. You can also deploy a pre-built image from any registry. Your app has to listen on 0.0.0.0 on the port Temps routes to.
When to use a Dockerfile
Temps auto-detects most frameworks and generates a Dockerfile for you. You only need a custom Dockerfile when:
- Your stack has no preset (e.g. Haskell or a custom C++ service). Many stacks that aren't auto-detected still build without one: see the .NET, Elixir, Rails and Laravel guides
- You need system packages that the generated image does not include
- You want a specific base image (Debian, Ubuntu, distroless)
- Your build has multiple stages with non-trivial caching
- You are migrating from another container platform and already have a Dockerfile
If a Dockerfile exists in your repository root, Temps always uses it — even if auto-detection would match a framework like Next.js or Flask. Remove or rename the file to fall back to auto-detection.
Quick start
Create a Dockerfile in your repository root:
Dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Push to your connected repository. Temps detects the Dockerfile, builds the image, runs health checks, and routes traffic to the new container.
The two requirements
- Listen on the port Temps routes to. In v0.1.0-beta.56, an
EXPOSEin your image decides the port and wins over the port configured for the project; withoutEXPOSE, Temps uses the configured port (default3000). From the next release, a configured port wins overEXPOSE. See Ports for the full order. - Listen on
0.0.0.0— Containers that bind tolocalhostor127.0.0.1only accept connections from inside the container. Temps routes traffic from the host, so your application must bind to all interfaces.
server.js
const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0');
Temps sets a PORT environment variable to the configured port (3000 if none is set), not to the EXPOSE value. If your image exposes a different port, make your app listen on that port, or set the project's port to match it under Project → Settings → Build & deploy → Deploy. The simplest setup is to EXPOSE and listen on the same port you configure.
Multi-stage builds
Multi-stage builds produce smaller images by separating the build toolchain from the runtime. Temps supports them natively.
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Custom Dockerfile path
If your Dockerfile is not in the repository root, specify its location in .temps.yaml:
.temps.yaml
build:
dockerfile: docker/Dockerfile.production
context: .
- Name
dockerfile- Type
- string
- Description
Path to the Dockerfile relative to the repository root. Defaults to
Dockerfile.
- Name
context- Type
- string
- Description
Build context directory relative to the repository root. Defaults to the project directory.
The build context controls which files are available during COPY and ADD instructions. Setting context: . when your Dockerfile is in a subdirectory ensures the build can access files from the repository root.
Build arguments
Pass build-time variables to your Dockerfile via .temps.yaml:
.temps.yaml
build:
args:
NODE_ENV: production
API_VERSION: v2
ENABLE_FEATURE_X: "true"
Use them in your Dockerfile with ARG:
Dockerfile
FROM node:22-alpine
ARG NODE_ENV=development
ARG API_VERSION=v1
ENV NODE_ENV=$NODE_ENV
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
Environment variables configured in the Temps dashboard are also passed as build arguments automatically. User-provided args take precedence over any args generated by presets.
Build arguments are visible in the image history. Do not use them for secrets. Use environment variables (set in the dashboard) for sensitive values — they are injected at runtime, not baked into the image.
.dockerignore
A .dockerignore file reduces build context size and prevents sensitive files from being included in the image:
.dockerignore
node_modules
.git
.env
.env.*
dist
build
*.md
.DS_Store
.vscode
.idea
Smaller build contexts mean faster builds. Always exclude node_modules, .git, and any build output that gets regenerated during the build.
Environment variables
Temps injects environment variables into your container at runtime. Variables configured in the dashboard are available via process.env (Node.js), os.environ (Python), os.Getenv (Go), etc.
Auto-injected variables
These variables are always available — you do not need to configure them:
- Name
PORT- Type
- string
- Description
The configured port (
3000unless you change it). See Ports for how it relates toEXPOSE.
- Name
HOST- Type
- string
- Description
Always
0.0.0.0.
- Name
SENTRY_DSN- Type
- string
- Description
Sentry DSN for error tracking, if configured on your project.
- Name
OTEL_EXPORTER_OTLP_ENDPOINT- Type
- string
- Description
OpenTelemetry collector endpoint for traces and metrics.
- Name
OTEL_SERVICE_NAME- Type
- string
- Description
Set to your project name. Used by OpenTelemetry SDKs automatically.
Health checks
After starting your container, Temps verifies it is healthy before routing traffic to it.
After your container starts, Temps sends HTTP GET requests to verify it is healthy before routing traffic to it.
- Path:
/(the root of your application) by default - Success: 2 consecutive responses with a 2xx, 3xx, 404 or 405 status code
- Timeout: 300 seconds (5 minutes) for the app to become healthy, fixed in v0.1.0-beta.56. If the app answers with error responses (other 4xx, or 5xx) for 60 seconds in a row, the deployment fails sooner. From the next release the timeout is configurable from 30 to 3600 seconds under Project → Settings → Build & deploy
- Retry interval: 2 seconds between successful checks, 5 seconds after an error response or a failed connection
Connection errors and other 4xx or 5xx responses are retried until the timeout; if the app hasn't passed by then, the deployment fails. The check runs once, at deploy time. To check a different path, add a .temps.yaml to your repository root:
health:
path: /healthOnly path is used. Other health fields (status, interval, timeout, retries) are accepted but currently have no effect.
/health endpoint that returns a simple 200. This avoids issues where / requires authentication or returns a redirect.Keep the health check fast. If your app takes a while to start, add a dedicated endpoint that answers as soon as the server is listening and point health.path at it.
A minimal health endpoint:
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
Pre-built images
You can deploy images that were built elsewhere — from Docker Hub, GitHub Container Registry, or any private registry.
Configure a project to use an external image instead of building from source. Temps pulls the image, runs health checks, and deploys it identically to a source-built container.
Your Docker daemon credentials (~/.docker/config.json on the Temps server) are used for authentication. For private registries, ensure the Temps server has docker login configured for your registry.
# Supported image reference formats
nginx:latest
ghcr.io/your-org/your-app:v1.2.3
registry.example.com:5000/app:latest
Pre-built image deployments skip the build step entirely. Temps pulls the image, inspects it for EXPOSE directives, and deploys it directly.
Resource limits
Every container runs with default resource limits:
| Resource | Default | Description |
|---|---|---|
| CPU | Uncapped | Set a limit in cores per environment |
| Memory | Uncapped | Set a limit in MB per environment (0 means uncapped) |
| PIDs | 512 | Maximum number of processes |
CPU and memory are uncapped until you set a limit for the environment in the dashboard. Only limits are applied; requests are recorded but not enforced. See Scaling.
Security hardening
Temps applies security best practices to every container automatically:
- All Linux capabilities dropped — containers run with the minimum privilege set
- No privilege escalation — the
no-new-privilegessecurity option is enforced - PID limit — prevents fork bombs with a 512-process cap
- Init process — proper signal handling and zombie process reaping via
--init - Log rotation — container logs are capped at 150 MB (3 files x 50 MB) to prevent disk exhaustion
Zero-downtime deployments
Temps deploys your new container before removing the old one. This applies to single-container deploys; a Docker Compose stack is stopped and restarted on each redeploy.
- Build — The new image is built from your latest commit
- Start — The new container starts alongside the existing one
- Health check — Temps verifies the new container is healthy
- Switch — The route table is updated to point to the new container
- Confirm — Temps waits for the proxy to confirm the route change (up to 60 seconds)
- Teardown — Only after confirmation, the old container is stopped and removed
If the route confirmation times out, Temps reverts the route table to the previous deployment. The old container keeps running. The failed deployment is marked as failed — nothing breaks. There is no automatic rollback after a deployment succeeds; to go back, redeploy a previous deployment as described in Rollbacks.
Networking
Containers run on an isolated Docker bridge network (temps-app-network). Each container:
- Gets a random host port mapped to its container port
- Is accessible from the Temps proxy via
127.0.0.1:{host_port} - Can communicate with other containers on the same network by container name
- Is not directly accessible from the internet — all external traffic goes through the Temps proxy
If your application needs to connect to a database or Redis service managed by Temps, use the service name as the hostname. Temps DNS resolution handles the rest within the Docker network.
Build caching
Temps uses Docker BuildKit for all image builds. BuildKit provides automatic layer caching — unchanged layers are reused from the cache, and you will see [CACHED] in the build logs for skipped steps.
To maximize cache hits:
Good: dependencies cached separately
FROM node:22-alpine
WORKDIR /app
# Copy dependency files first
COPY package.json package-lock.json ./
RUN npm ci
# Copy application code (changes more often)
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
Bad: cache busted on every change
FROM node:22-alpine
WORKDIR /app
# Copying everything first means any file change
# invalidates the npm ci cache
COPY . .
RUN npm ci
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
The key principle: copy files that change less often (lockfiles, dependency manifests) before files that change frequently (source code).
Troubleshooting
Build fails with "no such file or directory"
Your COPY or ADD instruction references a file outside the build context. Check your .temps.yaml build.context setting and make sure the file exists relative to the context directory.
Container exits immediately
Check the deployment logs for the exit code and error output. Common causes:
- Missing environment variable that your application requires at startup
- Port mismatch — your app listens on a different port than the one Temps routes to (see Ports)
- Missing runtime dependency — the production image is missing a library or binary the app needs
Health check times out
Your container starts but Temps cannot reach it on the expected port:
- Verify your application binds to
0.0.0.0, notlocalhost - Verify the port in your code matches the port Temps routes to (the
EXPOSEport on v0.1.0-beta.56) - Don't run migrations or other slow work before the server starts listening; the app has 5 minutes to become healthy, and a server that answers with errors for 60 seconds in a row fails the deploy sooner (from the next release the limit is a configurable Startup timeout (seconds) under Project → Settings → Build & deploy)
- Add a dedicated health endpoint that responds quickly, even while the app is still warming up, and set it as
health.pathin.temps.yaml
Image too large
Use multi-stage builds to separate build dependencies from the runtime image. A Node.js application that is 1.2 GB in a single stage typically shrinks to 150-200 MB with a multi-stage build.
Other tips:
- Use Alpine-based images (
node:22-alpineinstead ofnode:22) - Add a
.dockerignoreto excludenode_modules,.git, and test files - Clean up package manager caches (
apt-get clean,rm -rf /var/cache/apk/*) - Combine
RUNcommands to reduce layers