basecamp / kamal

Deploy web apps anywhere.
https://kamal-deploy.org
MIT License
9.38k stars 357 forks source link

Unable to Access http://localhost:3000 for Remix Run App container #844

Closed jfanals closed 2 weeks ago

jfanals commented 2 weeks ago

Description

I have successfully configured Kamal Deploy for a Rails application without any issues. However, I encountered a problem while trying to use Kamal Deploy to deploy a Remix Run application. The app container is correctly created and pulled to the server, but Docker is not making http://localhost:3000 accessible.

During the health check process, I was able to try to run the following command:

curl -f http://localhost:3000

and it always fails.

However, also during the health check process, the application is accessible if I point to the Docker network IP:

curl -f http://172.17.0.3:3000

Steps to Reproduce

  1. Configure Kamal Deploy for a Remix Run app. (e.g., Shopify Remix Template)
  2. Deploy the app using Kamal Deploy.
  3. Attempt to access http://localhost:3000 using curl (from the same server).

Configuration Changes

I have adjusted the health checks to point to:

healthcheck:
  path: /
  port: 3000

Despite this change, the Kamal Deploys are always unsuccessful due to health check failures, as http://localhost:3000 is not accessible.

Expected Behavior

I expected to be able to reach http://localhost:3000 in the same way I did with the Rails app.

Observed Behavior

Workaround

I was able to push the image to the server using

kamal build pull

also push the environmental variables with

kamal env push

and then boot it with

kamal app boot

but never managed to successfully complete the boot, as the health checks always fail.

Additional Information

FROM node:20-alpine

WORKDIR /app
COPY . .

ENV NODE_ENV=production

RUN npm install --omit=dev
RUN npm run build

EXPOSE 3000
CMD ["npm", "run", "docker-start"]

Dockerfile Reference

Any guidance on resolving this issue would be greatly appreciated.

scart88 commented 2 weeks ago

Hi,

You will need to add curl to your Dockerfile:

Since you are using Node alpine, you can add it like this RUN apk add --no-cache curl

FROM node:20-alpine

WORKDIR /app
COPY . .

RUN apk add --no-cache curl

ENV NODE_ENV=production

RUN npm install --omit=dev
RUN npm run build

EXPOSE 3000
CMD ["npm", "run", "docker-start"]
jfanals commented 2 weeks ago

Thanks @scart88 for the quick response.

Sorry for the confusion. The health check tests (using curl) are run on the target server (not inside the container). As mentioned, curl works fine for accessing the Docker network IP http://172.17.0.3:3000. The issue is that http://localhost:3000 cannot be reached.

Any additional guidance on why http://localhost:3000 is not accessible and how to resolve this would be greatly appreciated.

scart88 commented 2 weeks ago

I'm unsure how Remix works; however, you will still need to add curl inside the docker container.

You could also try to add these inside your Dockerfile:

ENV HOST=0.0.0.0
ENV PORT=3000

EXPOSE 3000

I'm using Astro on one project and inside my astro.config.mjs, I have to set server: { host: true }

 server: {
    host: true,
    hmr: { clientPort: 3000 },
    port: 3000,
    watch: { usePolling: true }
  },
  adapter: node({
    mode: "standalone"
  })

You try to add to your vite.config.js

server: { host: '0.0.0.0', },

Anyway, adding curl inside your docker container is required for the healthchecks

jfanals commented 2 weeks ago

I tried adding curl in the Dockerfile, but that did not work.

When I try to run (without Kamal) the same container with:

docker run -p 3000:3000 container_image

Then I am able to access the container using:

curl -f http://localhost:3000

So the port 3000 is reachable in the container from the server, but only if the docker run command is configured correctly.

scart88 commented 2 weeks ago

You can ssh into your machine and run docker ps -a to find the latest unhealthy container ID. Then you can run docker inspect --format "{{json .State.Health }}" your-container-ID and see the container logs.

jfanals commented 2 weeks ago

@scart88 you were totally right that the cause of the issue is based on curl not being found.

After running your suggested command, I got this output:

# docker inspect --format "{{json .State.Health }}" 2e100655d512
{"Status":"unhealthy","FailingStreak":11,"Log":[{"Start":"2024-06-20T05:54:31.025192123Z","End":"2024-06-20T05:54:31.086560615Z","ExitCode":1,"Output":"/bin/sh: curl: not found\n"},{"Start":"2024-06-20T05:54:36.087243907Z","End":"2024-06-20T05:54:36.151146351Z","ExitCode":1,"Output":"/bin/sh: curl: not found\n"},{"Start":"2024-06-20T05:54:41.154865914Z","End":"2024-06-20T05:54:41.20236162Z","ExitCode":1,"Output":"/bin/sh: curl: not found\n"},{"Start":"2024-06-20T05:54:46.204630404Z","End":"2024-06-20T05:54:46.281182879Z","ExitCode":1,"Output":"/bin/sh: curl: not found\n"},{"Start":"2024-06-20T05:54:51.28534627Z","End":"2024-06-20T05:54:51.3439735Z","ExitCode":1,"Output":"/bin/sh: curl: not found\n"}]}

The strange thing is that curl is in the image

# docker run -it --rm 11d7663bd9ec /bin/sh
/app # curl --version
curl 8.7.1 (x86_64-alpine-linux-musl) libcurl/8.7.1 OpenSSL/3.3.0 zlib/1.3.1 brotli/1.1.0 zstd/1.5.6 c-ares/1.28.1 libidn2/2.3.7 libpsl/0.21.5 nghttp2/1.62.0
Release-Date: 2024-03-27
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz NTLM PSL SSL threadsafe TLS-SRP UnixSockets zstd

I even added a version check during the build process, to make sure it was there

FROM node:20-alpine

WORKDIR /app
COPY . .

RUN apk add --no-cache curl && curl --version

And it does show in the build process

DEBUG [223b1a4f]       #9 [4/8] RUN apk add --no-cache curl && curl --version
 DEBUG [223b1a4f]       #9 0.167 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
 DEBUG [223b1a4f]       #9 0.360 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
 DEBUG [223b1a4f]       #9 0.972 (1/10) Installing ca-certificates (20240226-r0)
 DEBUG [223b1a4f]       #9 1.010 (2/10) Installing brotli-libs (1.1.0-r2)
 DEBUG [223b1a4f]       #9 1.027 (3/10) Installing c-ares (1.28.1-r0)
 DEBUG [223b1a4f]       #9 1.031 (4/10) Installing libunistring (1.2-r0)
 DEBUG [223b1a4f]       #9 1.052 (5/10) Installing libidn2 (2.3.7-r0)
 DEBUG [223b1a4f]       #9 1.058 (6/10) Installing nghttp2-libs (1.62.0-r0)
 DEBUG [223b1a4f]       #9 1.064 (7/10) Installing libpsl (0.21.5-r1)
 DEBUG [223b1a4f]       #9 1.068 (8/10) Installing zstd-libs (1.5.6-r0)
 DEBUG [223b1a4f]       #9 1.080 (9/10) Installing libcurl (8.7.1-r0)
 DEBUG [223b1a4f]       #9 1.091 (10/10) Installing curl (8.7.1-r0)
 DEBUG [223b1a4f]       #9 1.098 Executing busybox-1.36.1-r28.trigger
 DEBUG [223b1a4f]       #9 1.110 Executing ca-certificates-20240226-r0.trigger
 DEBUG [223b1a4f]       #9 1.203 OK: 16 MiB in 26 packages
 DEBUG [223b1a4f]       #9 1.377 curl 8.7.1 (x86_64-alpine-linux-musl) libcurl/8.7.1 OpenSSL/3.3.0 zlib/1.3.1 brotli/1.1.0 zstd/1.5.6 c-ares/1.28.1 libidn2/2.3.7 libpsl/0.21.5 nghttp2/1.62.0
 DEBUG [223b1a4f]       #9 1.378 Release-Date: 2024-03-27
 DEBUG [223b1a4f]       #9 1.378 Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp ws wss
 DEBUG [223b1a4f]       #9 1.378 Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz NTLM PSL SSL threadsafe TLS-SRP UnixSockets zstd
 DEBUG [223b1a4f]       #9 DONE 1.4s

I will investigate further how to make sure that the health check is able to locate curl.

jfanals commented 2 weeks ago

After further investigation I was loading a cached docker image. After clearing everything and with the suggestion from @scart88 of adding curl to the Dockerfile it everything works 👍

It would have helped if kamal could check if curl is available in the image and if not let the user know.