Image deploys
Image deploys hand the stack a pre-built Docker image instead of source code. The stack pulls the image and runs it. Faster deploys, build-once-deploy-many semantics, and your CI gets to build images on bigger machines than your stack.
When to pick image deploys over git deploys
| Pick image deploys when | Pick git deploys when |
|---|---|
| Builds are slow and you want them off the stack | You want one obvious source of truth: your branch tip |
| You're already building images in CI | You don't have a registry yet |
| You want to deploy the same image to multiple stacks atomically | You're using buildpacks and don't want a Dockerfile |
| You need build secrets (npm tokens, private package registries) you don't want on the stack | You're prototyping and the build is fast enough on the stack anyway |
Trigger
$ ownstack deploy --image # latest tag from registry default
$ ownstack deploy --image=ghcr.io/me/my-app:v1.2.3
The image must be pullable from the stack. For private registries, configure the credential in the dashboard (Stack → Registry credentials) once; the stack's docker daemon uses it.
Image requirements
- Web process binds to
$PORT— the stack tells the container which port to listen on at runtime, just like buildpack apps. - One CMD / one ENTRYPOINT per image — corresponds to the
webprocess. For multi-process apps, declare process types viaapp.json'sscriptsor via Procfile-in-image. - No HEALTHCHECK required — dokku adds its own probe.
- Linux/amd64 by default. ARM64 support depends on the stack's instance architecture.
Multi-arch images
If you build with docker buildx for both amd64 and arm64, the registry stores a manifest list and the stack pulls the matching arch automatically. Your CI looks like:
docker buildx build --platform linux/amd64,linux/arm64 \
-t ghcr.io/me/my-app:$SHA \
-t ghcr.io/me/my-app:latest \
--push .
Tagging strategy
Two patterns work well:
- Immutable SHA tags: tag with
$GITHUB_SHAand deploy that tag. Easy rollback — old SHAs are still in the registry. - Latest + version: tag both
latestandv1.2.3; deployv1.2.3for prod,latestfor staging.
Don't deploy latest to prod without a version pin. latest moves; reproducible deploys care which SHA was running.
CI hookup (GitHub Actions example)
name: Deploy
on:
push: { branches: [main] }
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: |
docker buildx build --push \
--platform linux/amd64 \
-t ghcr.io/${{ github.repository }}:${{ github.sha }} \
-t ghcr.io/${{ github.repository }}:latest .
- run: |
curl -sSL https://ownstack.org/cli/install.sh | bash
export PATH="$HOME/bin:$PATH"
ownstack login --token ${{ secrets.OWNSTACK_TOKEN }}
ownstack deploy --app=my-app --image=ghcr.io/${{ github.repository }}:${{ github.sha }}