Deploying Your First App
Pick your runtime and configure your build. HostStack handles the rest.
Supported Runtimes
Select your runtime when creating a service. HostStack auto-detects build tools where possible. You can also bring your own Dockerfile for any language.
Build & Start Commands
Configure your install, build, and start commands in service settings. Here are common examples:
Build Command: npm run build
Start Command: npm startBuild Command: pip install -r requirements.txt
Start Command: gunicorn app:app --bind 0.0.0.0:$PORTBuild Command: go build -o server .
Start Command: ./serverBuild Command: bundle install
Start Command: bundle exec rails server -p $PORTBuild Command: mix deps.get && mix release
Start Command: _build/prod/rel/my_app/bin/my_app startBuild Command: dotnet publish -c Release -o out
Start Command: dotnet out/MyApp.dllBuild Command: composer install --no-dev
Start Command: php -S 0.0.0.0:$PORT -t publicBuild Command: ./mvnw -q -DskipTests package
Start Command: java -jar target/my-app.jar --server.port=$PORTDocker Deploys
If your language isn't natively supported or you need custom system dependencies, add a Dockerfile to your repo and select the Docker runtime. HostStack will build your image and deploy the resulting container.
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE $PORT
CMD ["node", "server.js"]Zero-Downtime Deploys
HostStack uses zero-downtime rolling deploys. When you deploy, a new container starts alongside the current one. Once the new container passes health checks, traffic switches over. The old container is gracefully stopped. Your users never see downtime.
When rolling doesn't work: the recreate strategy
Rolling is the right default, and it is the only strategy most services ever need. But it cannot deploy a single container that holds an exclusive lock on a mounted volume — a database running inside your container, an embedded queue, anything with a lockfile or a fixed host port on shared storage. The failure is a deadlock, not an error:
- The new container starts with the same volume mounted as the old one.
- Its process can't open the data directory — the outgoing container still holds the lock — so it exits.
- Without that process, the new container never becomes healthy.
- Because it never becomes healthy, the old container is never stopped.
- Because the old one is never stopped, the lock is never released.
That loop runs for the full health-check grace period and then fails. Set Settings → Runtime → Deploy Strategy to Recreate (or deploy_strategy: "recreate" via the API, SDK or MCP) and the platform stops and removes the old container before starting the new one. The service is briefly unavailable between the two — that is the trade, and it is why this is opt-in per service.
Environment Variables
Set environment variables in the Settings tab. They're encrypted at rest and available during build and runtime. See the Environment Variables guide for details.
Health Checks
HostStack checks your service is healthy before routing traffic. Set a health check path (e.g., /health) in your service settings. The endpoint should return HTTP 200 when your app is ready.
healthcheck:
path: /health
interval: 10s
timeout: 5s
retries: 3If no health check path is set, HostStack will check that your container starts and listens on the configured port.
Build Cache
HostStack caches your build dependencies between deploys. For Node.js, this means node_modules is cached. For Python, pip packages are cached. This significantly speeds up subsequent builds. Cache is cleared when your lock file changes.
Database Migrations
Run your migrations as part of the service start command (e.g., npm run migrate && node server.js). They execute inside the new container before traffic switches, so a broken migration fails the deploy and the previous version keeps serving.
Each successful application emits a migration.applied activity-log event (visible in the dashboard activity feed and via the MCP list_activity_log tool), so you can tell a "no-op redeploy" apart from a "ran a fresh migration" without grepping deploy logs.
Seed migrations bypass app-layer caches
When a migration writes data directly to Postgres (seed rows, content backfills, lookup-table updates), it doesn't go through your application code, so any Redis / in-memory cache that fronts those rows will keep serving stale data until it expires or is explicitly flushed. If your app caches the affected reads, flush the relevant prefix at the end of the migration script:
// At the end of your data-seed migration:
import { redis } from './redis';
await redis.del(...(await redis.keys('seo:*')));Troubleshooting
Build fails with "command not found"
Ensure your build command matches your runtime. For Node.js, check that your package.json has a build script defined.
App crashes on startup with "EADDRINUSE"
Your app must listen on the port specified by the PORT environment variable. Don't hardcode a port number.
Container killed: out of memory (OOM)
Your service is exceeding its size's memory limit. Resize to a larger instance size, reduce memory usage, or check for memory leaks in your application.
Build is slow (>5 minutes)
Check if your .gitignore excludes large files. Make sure you're using a lock file (package-lock.json, yarn.lock, etc.) for consistent, cached installs.
Next: Custom Domains