Apps ·
Deploy any Docker image with Container App
Order a Container App, deploy a public image, then swap in your own: non-root, port, amd64, no local disk, environment variables, private registries and releases.
Container App runs any image you can pull from a registry: a Node or Next.js server, a Python API, a Go binary, or a tool nobody has packaged for the catalogue yet. You give it an image and a port; the platform adds the address, the TLS certificate and the restarts. This guide deploys a public image first so you see the whole flow, then covers what your own image needs.
What you need
- A Headpat Space account with a verified email address and your billing details filled in.
- An image on a registry such as Docker Hub or GitHub Container Registry. If you only have source code, Container App can build it for you: pick Upload code or From GitHub instead of Direct image in step 1. This guide sticks to images.
1. Order a Container App
- Sign in to the panel, open Apps in the sidebar and click Create App.
- Under Template, search for Container App and select it.
- Under Package, choose a package and set the CPU and memory your image needs. You can change both later.
- Under Details, fill in a Display name.
- Under App source, keep Direct image selected and fill in:
- Container image: the full reference, including registry and tag. For a first test use
docker.io/nginxinc/nginx-unprivileged:1.27. - Container port: the port your app listens on inside the container. For the test image that is
8080. - Leave Start command and Arguments empty unless the image needs them.
- Leave Persistent storage empty for the test image; step 2 explains when to fill it in.
- Container image: the full reference, including registry and tag. For a first test use
- Tick the box confirming that provisioning may start right away, click Deploy app and complete the payment.
The image is pulled and started. On the app page, the Access card shows the address of your app. Wait until it says HTTPS active, then click Open. With the test image you get the nginx welcome page over HTTPS, on an address that is yours from now on.
2. Swap in your own image
Open the Configuration card on the app page. It has the same fields as the order form: Container image, Container port, Start command, Arguments, Environment variables and Private registry credentials. Change what you need and click Apply & redeploy. The app restarts with the new settings and keeps its address.
Before you point it at your own image, check these four things. They cause nearly every failed first deploy.
It must run as a non-root user
Containers start as a normal user here, and an image that wants to be root is refused. Your Dockerfile needs a USER line, and the directories the process writes to must belong to that user. Most language base images and most well-maintained tools already do this. Some popular official images do not: nginx, httpd and caddy start as root, which is why the test above uses nginx-unprivileged. If your app stays at 0/1 ready on the Status card and never reaches HTTPS active, this is the first thing to check; the Logs & diagnostics card on the app page names the reason.
It must listen on all interfaces, on port 1024 or higher
Bind to 0.0.0.0, not 127.0.0.1 or localhost; a server that only listens on localhost cannot be reached from outside the container. Ports below 1024 need root, so use something like 3000 or 8080 and enter that same number under Container port. The platform checks that port with a plain TCP connection: as soon as your app accepts connections it is marked ready and traffic flows.
It must be built for linux/amd64
Our servers are x86-64. On an Apple Silicon Mac, docker build produces an arm64 image by default, and that will not start here. Build with docker build --platform linux/amd64 -t ghcr.io/you/app:1.0 . or push a multi-platform image.
Give it a volume if it keeps data on disk
A Container App has no disk of its own unless you give it one. Fill in Persistent storage with the folder your app writes to, for example /data: that folder keeps its contents across restarts and redeploys and is included in the app's backups. Everything outside it is gone when the app is redeployed or restarted, so treat the rest of the container as disposable. Databases still belong in a database app such as PostgreSQL or MariaDB: see Create a PostgreSQL database and connect to it. An app with a volume restarts in place on every redeploy, so expect a few seconds of downtime per release.
3. Configuration and secrets
Use Environment variables for everything that differs between your laptop and production: database URLs, API keys, the public URL of the app. Click Add variable, enter the name and the value, then apply. Values are stored on the platform and handed to the container when it starts; they never have to go into the image.
If your framework needs to know its own public URL (Next.js, Django and Rails often do), copy the address from the Access card into the matching variable.
4. Images on a private registry
If your image is not public, paste a Docker config JSON under Private registry credentials. The shape is:
{"auths": {"ghcr.io": {"auth": "BASE64"}}}
BASE64 is username:token encoded with base64, for example echo -n 'you:ghp_xxx' | base64. For Docker Hub the key is https://index.docker.io/v1/. Use a token that can only pull, not your account password. Once saved, the Configuration card shows credentials set; paste a new JSON there to replace them.
5. Ship a new version
- Build and push the new image under a new tag, for example
ghcr.io/you/app:1.1. - Open the Configuration card, change the tag under Container image and click Apply & redeploy.
Use a new tag for every release. Pushing to the same tag again does not reliably fetch the new image. If you do use latest, the Restart button at the top of the app page pulls the newest latest and restarts.
Use your own domain
A Container App can carry your own domain with a certificate. See Connect your own domain to an app.
Good to know
- The Logs & diagnostics card on the app page shows what the container prints and, when it is not running, why: a pull error, a refused root user, a crash with its exit code. Still test the image locally first with
docker run --rm -p 8080:8080 your-image; a problem you can see on your laptop is faster to fix there. - One instance runs at a time. On a redeploy the new container is started before the old one is stopped, so short restarts rarely cause downtime, but your app should start quickly and shut down cleanly on SIGTERM.
- The memory you pick is a hard limit. An app that grows past it is restarted; raise the memory on the package if that happens.
Updated October 18, 2026