Search code examples
dockerherokufastapi

Deploy FastAPI to Heroku with Docker Container Registry


The Problem

I am deploying a Docker image running FastAPI to Heroku using the Docker Container Registry deployment method. When I deploy the container I get the following logs when I run heroku logs --app <app-name> --tail

2024-06-03T20:57:32.222808+00:00 app[api]: Release v29 created by user [email protected]
2024-06-03T20:57:32.222808+00:00 app[api]: Deployed web (228f5e77d4e2) by user [email protected]
2024-06-03T20:57:45.637479+00:00 heroku[web.1]: Starting process with command `/bin/sh -c \"./start.sh\ \$\{PORT\}\"`
2024-06-03T20:57:46.235100+00:00 heroku[web.1]: Process exited with status 127
2024-06-03T20:57:46.185240+00:00 app[web.1]: /bin/sh: 1: ./start.sh 53182: not found
2024-06-03T20:57:46.255899+00:00 heroku[web.1]: State changed from starting to crashed

In the Heroku UI I can see the command for starting a web process is web /bin/sh -c \"./start.sh\ \$\{PORT\}\" which is correct as that command works locally. I've even exec'd into the container and run /bin/sh -c "./start.sh <port>" to verify it will work, and it does (meaning I can hit any and all of my API endpoints and get the expected results).

The Details

Dockerfile

FROM node:21 as ui-build

ENV PORT $PORT

WORKDIR /opt/ui

COPY ui .
RUN npm ci && npm run build

FROM python:3.11 as final-build

WORKDIR /opt/app
COPY --from=ui-build /opt/ui/build ./ui/build
COPY server server

RUN cd server && pip install -r requirements.txt

# Apparently Heroku doesn't respect the EXPOSE directive.
EXPOSE $PORT

WORKDIR /opt/app/server
COPY ./start.sh .

# Not sure why these commands are not working but the start.sh script seems to be going in the right direction.
# ENTRYPOINT [ "uvicorn", "api.app:app" ]
# CMD [ "--host", "0.0.0.0", "--port", $PORT ]
CMD "./start.sh ${PORT}"

I'm using the gonuit/heroku-docker-deploy Github Action to build and push the docker image to Heroku's container registry. Here is how I use the action in my workflow.

name: Build and Push Image

on:
  push:
    branches:
      - main

jobs:
  build-image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build and Push to Heroku
        uses: gonuit/[email protected]
        with:
          email: [email protected]
          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
          heroku_app_name: <app-name>

The Links

These are some of the links I've used to come up with code I currently have but neither has worked for me.

This final link is the official Heroku Docs on deploying w/ Docker Container Registry which to the best of my knowledge I've implemented identically.

The Solution

An ideal solution for me would be able to deploy my application using Docker, and Github Actions. If I need to add a Procfile or Heroku.yml file to the application I'm alright with that it just seems to be something that's not needed for the deployment method I'm using.


Solution

  • My answer is basically a re-skin of this answer. https://stackoverflow.com/a/67747986/1464160

    The correct way to define the CMD directive is

    CMD [ "sh", "-c", "uvicorn api.app:app --host=0.0.0.0 --port=${PORT:-8000}" ]
    

    Of course the path api.app:app and the default port # can be changed to whatever you want it to be. The important part seems to be having "sh", "-c" at the beginning of the CMD directive.