ASTRODOCK documentation Home GitHub

Building apps

Dockerfile apps

Most apps fit the Node buildpack just fine. But when you need a different language, an unusual runtime, or full control over how the app is built, you can hand Astrodock a Dockerfile instead. The platform builds your image and runs it as a container, and the whole subdomain is yours.

When to use a Dockerfile

  • Non-Node runtimes — Python, Go, Ruby, anything that isn't Node.
  • Full control over system packages, build steps, or the process model.
  • Existing containerized apps you'd rather not rewrite to fit the buildpack.

If your app is a normal Node frontend + server, you almost certainly want the buildpack instead — see App structure & app.json. Use Docker when the buildpack can't express what you need.

Switch the runtime to docker

One field flips an app from the Node buildpack to the container path:

"runtime": {
  "type": "docker",
  "dockerfile": "Dockerfile"
}

runtime.dockerfile is optional — it defaults to Dockerfile at the deploy root. Set it only if your Dockerfile has a different name or lives elsewhere (relative to source.repoPath).

The container contract

Whatever's inside your container, two rules from the buildpack still apply — the platform uses them to route traffic and to know your app is alive:

  1. Listen on ASTRODOCK_PORT

    The platform injects ASTRODOCK_PORT (and a PORT alias) into the container and proxies the subdomain to it. Your process must bind that port — don't hardcode one.

  2. Answer GET /health with a 200

    After the container starts, the platform probes /health. A 200 marks the deploy healthy; without it the deploy is recorded as failed.

Be the foreground process

Your container's main process must be the server itself (so the platform can supervise and restart it). If the container exits or the app is unreachable, double-check it's listening on ASTRODOCK_PORT and running in the foreground.

A minimal example

A tiny Python web service. It reads the injected port and serves both /health and the app on it:

# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# The platform injects ASTRODOCK_PORT (and PORT) — bind it, don't hardcode.
CMD ["python", "main.py"]
# main.py (Flask)
import os
from flask import Flask

app = Flask(__name__)

@app.get("/health")
def health():
    return {"status": "ok"}

@app.get("/")
def home():
    return f"Hello from {os.environ['ASTRODOCK_APP_NAME']}"

if __name__ == "__main__":
    port = int(os.environ.get("ASTRODOCK_PORT", os.environ.get("PORT", 8000)))
    app.run(host="0.0.0.0", port=port)

And the matching app.json:

{
  "schemaVersion": "1",
  "slug": "py-tool",
  "name": "Python Tool",
  "subdomain": "pytool",
  "source": { "branch": "main", "repoPath": "service" },
  "runtime": { "type": "docker", "dockerfile": "Dockerfile" },
  "auth":     { "mode": "platform" },
  "database": { "mode": "internal" },
  "storage":  { "mode": "none" },
  "env": [
    { "key": "WORKERS", "required": false, "default": "2",
      "description": "Number of worker processes" }
  ]
}

Note repoPath: "service" — the build happens in that subdirectory, so the Dockerfile and code live under service/ in the repo.

How environment is injected

Every variable a buildpack app would get, a Docker app gets too — they're passed into the container as environment variables when it runs. That means inside your container you can read:

  • The always-present vars: ASTRODOCK_APP_SLUG, ASTRODOCK_APP_NAME, ASTRODOCK_APP_URL, ASTRODOCK_BASE_DOMAIN, ASTRODOCK_PORT, ASTRODOCK_ENV (plus the PORT alias).
  • Resource and auth vars, depending on your modes (e.g. ASTRODOCK_DATABASE_URL when database isn't none).
  • Your own declared env[] variables.

Read them from the environment exactly as you would anywhere else — the full catalog is in the app.json & env reference. Don't bake secrets into the image; let the platform inject them at run time.

Resource modes still apply

Choosing docker changes only how the app is built and run — not what resources it can use. auth, database, and storage work exactly the same as for a Node app: pick internal, external, or none, and the matching variables are injected. An external mode still triggers the required-variable gate before the build runs.

Differences from the Node buildpack

Node buildpackDockerfile
Layoutapp/ + server/ (or a standalone variant)No split — your container serves everything
RoutingStatic frontend at the URL; /api/* proxied to the serverThe whole subdomain is proxied to your container
Static filesCaddy serves your built app/dist/Your container serves its own assets
Buildnpm ci + buildCommand / npm installdocker build from your Dockerfile
RunA Node processA container

No /api requirement for Docker apps

Because the whole subdomain goes to your container, you're free to route however you like — the /api/* namespacing is a Node-buildpack rule, not a Docker one. You still must bind ASTRODOCK_PORT and answer /health.

Next steps

Related