Still getting stuck here, I just can't

Still getting stuck here, I just can't figure out why it doesn't like my setup. - My app is definitely running on 8080 - My image is within all limits - If I pull and run the image locally everything works completely fine I feel like my only option is to strip my entrypoint down to nothing and slowly add stuff until it breaks again (assuming that's even the problem? 😭)
15 Replies
frolic
frolic2mo ago
Are there container logs in the dash?
Phatso
PhatsoOP2mo ago
Only these :(
No description
Phatso
PhatsoOP2mo ago
if my entrypoint had some issue with it, I'd expect to at least see my startup echos, I don't know if it's even getting to that point? idk unless I shouldn't expect to see any of those logs here? i have observability turned on, is there anything else i can do to see more output, do you know? wrangler logs, dashboard logs - nothing's really giving me any clues
frolic
frolic2mo ago
if your container is logging you should see it there. Can you give me the id?
Phatso
PhatsoOP2mo ago
workers/containers/applications/<> ?
frolic
frolic2mo ago
that shows up from npx wrangler containers list
Phatso
PhatsoOP2mo ago
a03d188f-8b8b-4862-a8cc-4254f59e49e1
a03d188f-8b8b-4862-a8cc-4254f59e49e1
frolic
frolic2mo ago
well I can see that it is running at least. Trying to see if I can find the problem. Do you log anything on startup?
Phatso
PhatsoOP2mo ago
Maybe not immediately but yeah, before I run anything that should be able to error for sure
#!/bin/bash

home=/home/<snip>
<snip>=$home/<snip>
<snip>=$home/<snip>/<snip>
<snip>="${ENV_VAR:-live}"

<snip>="${ANOTHER_ENV_VAR:-sandbox}"
<snip>="${ANOTHER_ONE:-0}"
<snip>="${YET_ANOTHER:-example_string}"
echo "Starting the server with <snip>: $<snip>"
#!/bin/bash

home=/home/<snip>
<snip>=$home/<snip>
<snip>=$home/<snip>/<snip>
<snip>="${ENV_VAR:-live}"

<snip>="${ANOTHER_ENV_VAR:-sandbox}"
<snip>="${ANOTHER_ONE:-0}"
<snip>="${YET_ANOTHER:-example_string}"
echo "Starting the server with <snip>: $<snip>"
these are the first few lines of my entrypoint
frolic
frolic2mo ago
My other thought is how are you accessing the container in your code? is it possible your spinning up another container but never going back to the same one?
Phatso
PhatsoOP2mo ago
Yeah the worker code is definitely the piece I'm scratching my head on the most. All of the examples are great but they're kinda simplistic, too I assign each new user a UUID and use that as the container ID. they send me their UUID with all future requests, so I think it should be going to the same place let me pull out some relevant pieces.. (also there were zero examples of how to type my stuff properly so I've really been winging it)
import { getContainer, Container } from "@cloudflare/containers";

// (no idea what type these are supposed to be - I don't think they're Containers?)
// (maybe DurableObjectSomething<Container>?)
export interface Env {
CONTAINER_A: Container;
CONTAINER_B: Container;
CONTAINER_C: Container;
CONTAINER_D: Container;
LOG_BUCKET: R2Bucket;
}

export class BaseRunner extends Container {
defaultPort = 8080;
sleepAfter = "3m";
}
export class ContainerA extends BaseRunner {}
export class ContainerB extends BaseRunner {}
export class ContainerC extends BaseRunner {}
export class ContainerD extends BaseRunner {}

const findContainer = (env: Env, sessionId: string, branch: string): DurableObjectStub<Container> => {
let containerType: any;
switch (branch) {
case "A":
containerType = env.CONTAINER_A
break;
case "B":
containerType = env.CONTAINER_B
break;
case "C":
containerType = env.CONTAINER_C
break;
case "D":
containerType = env.CONTAINER_D
break;
default:
containerType = env.CONTAINER_A;
}

console.log(`Fetching container for session ID: ${sessionId}, branch: ${branch}`);
const container = getContainer(containerType, sessionId);
console.log(`Container fetched: ${container}`);

if (!container) {
throw new Error(`Container with session ID ${sessionId} not found.`)
}

return container
}

const createRequest = (endpoint: string, method: string = "GET", body?: string): Request => {
// is this the right way to set the URL? I only saw examples of people passing entire `Request` objects through from the initial worker `fetch`
const url = `http://127.0.0.1:8080/cgi-bin/${endpoint}`;
console.log(`Creating request for endpoint: ${url}, method: ${method}, body: ${body}`);
return new Request(url, { method, body });
}

...

const handleStartSession = async (env: Env, branch: string | null): Promise<Response> => {
if (!branch) {
return new Response("Branch type is required", { status: 400 });
}

const sessionId = crypto.randomUUID();
const container = findContainer(env, sessionId, branch);
// tried `await container.start()` and `await container.startAndWaitForPorts({with a few different config options})
// neither seemed to make a difference - decided to try relying on the autostart feature; it should start when the client makes their first "healthcheck" request?

return new Response(JSON.stringify({ sessionId }), {
headers: { "Content-Type": "application/json" },
});
};

const handleHealthCheck = async (env: Env, sessionId: string, branch: string): Promise<Response> => {
const container = findContainer(env, sessionId, branch);
const containerRequest = createRequest("healthcheck.sh");

const containerResponse = await container.containerFetch(containerRequest); // also tried `await container.fetch(containerRequest)`
return new Response(null, { status: containerResponse.status });
}
import { getContainer, Container } from "@cloudflare/containers";

// (no idea what type these are supposed to be - I don't think they're Containers?)
// (maybe DurableObjectSomething<Container>?)
export interface Env {
CONTAINER_A: Container;
CONTAINER_B: Container;
CONTAINER_C: Container;
CONTAINER_D: Container;
LOG_BUCKET: R2Bucket;
}

export class BaseRunner extends Container {
defaultPort = 8080;
sleepAfter = "3m";
}
export class ContainerA extends BaseRunner {}
export class ContainerB extends BaseRunner {}
export class ContainerC extends BaseRunner {}
export class ContainerD extends BaseRunner {}

const findContainer = (env: Env, sessionId: string, branch: string): DurableObjectStub<Container> => {
let containerType: any;
switch (branch) {
case "A":
containerType = env.CONTAINER_A
break;
case "B":
containerType = env.CONTAINER_B
break;
case "C":
containerType = env.CONTAINER_C
break;
case "D":
containerType = env.CONTAINER_D
break;
default:
containerType = env.CONTAINER_A;
}

console.log(`Fetching container for session ID: ${sessionId}, branch: ${branch}`);
const container = getContainer(containerType, sessionId);
console.log(`Container fetched: ${container}`);

if (!container) {
throw new Error(`Container with session ID ${sessionId} not found.`)
}

return container
}

const createRequest = (endpoint: string, method: string = "GET", body?: string): Request => {
// is this the right way to set the URL? I only saw examples of people passing entire `Request` objects through from the initial worker `fetch`
const url = `http://127.0.0.1:8080/cgi-bin/${endpoint}`;
console.log(`Creating request for endpoint: ${url}, method: ${method}, body: ${body}`);
return new Request(url, { method, body });
}

...

const handleStartSession = async (env: Env, branch: string | null): Promise<Response> => {
if (!branch) {
return new Response("Branch type is required", { status: 400 });
}

const sessionId = crypto.randomUUID();
const container = findContainer(env, sessionId, branch);
// tried `await container.start()` and `await container.startAndWaitForPorts({with a few different config options})
// neither seemed to make a difference - decided to try relying on the autostart feature; it should start when the client makes their first "healthcheck" request?

return new Response(JSON.stringify({ sessionId }), {
headers: { "Content-Type": "application/json" },
});
};

const handleHealthCheck = async (env: Env, sessionId: string, branch: string): Promise<Response> => {
const container = findContainer(env, sessionId, branch);
const containerRequest = createRequest("healthcheck.sh");

const containerResponse = await container.containerFetch(containerRequest); // also tried `await container.fetch(containerRequest)`
return new Response(null, { status: containerResponse.status });
}
tried to include all of the main worker<>container mechanisms in there, got a bit length my b
frolic
frolic2mo ago
Where you log the creating request for endpoint is the body undefined? And what is the goal with the CONTAINER_X differentiation?
Phatso
PhatsoOP2mo ago
I have a few different flavors of containers, the user can select which one they want to use for their session
"logs": [
{
"message": [
"Health check endpoint hit"
],
"level": "log",
"timestamp": 1750878876951
},
{
"message": [
"Fetching container for session ID: 7df81f8b-3666-4770-b622-52d6c138639a, branch: public"
],
"level": "log",
"timestamp": 1750878876951
},
{
"message": [
"Container fetched: [object DurableObject]"
],
"level": "log",
"timestamp": 1750878876951
},
{
"message": [
"Creating request for endpoint: http://127.0.0.1:8080/cgi-bin/healthcheck.sh, method: GET, body: undefined"
],
"level": "log",
"timestamp": 1750878876951
}
],
"logs": [
{
"message": [
"Health check endpoint hit"
],
"level": "log",
"timestamp": 1750878876951
},
{
"message": [
"Fetching container for session ID: 7df81f8b-3666-4770-b622-52d6c138639a, branch: public"
],
"level": "log",
"timestamp": 1750878876951
},
{
"message": [
"Container fetched: [object DurableObject]"
],
"level": "log",
"timestamp": 1750878876951
},
{
"message": [
"Creating request for endpoint: http://127.0.0.1:8080/cgi-bin/healthcheck.sh, method: GET, body: undefined"
],
"level": "log",
"timestamp": 1750878876951
}
],
yeah in most cases it will be I'm not eactly sure what fixed my issue (there were a few) but I think my main issue was a small typo. in my entrypoint, I had:
exec webserver -v -p 8080 &
exec webserver -v -p 8080 &
so it was execing the session and then putting it in the background, which I think got it terminated immediately. i removed the exec and that (possibly with a few other changes?...) got the container starting and producing logs 👍
frolic
frolic2mo ago
oh nice. I was in the process of typing up a long response 😅 . I'll send what I had anyways though just in case it is useful. Feel free to ignore if not though. I did something similar to this in the past by having a manger DO that dealt with storing state/routing to existing containers. The contructor looked something like:
export type ContainerBindingMap = Record<string, DurableObjectNamespace>;
export class CommonContainerManager extends DurableObject<Env> {
constructor(
ctx: DurableObjectState,
env: Env,
bindings: ContainerBindingMap,
) {
super(ctx, env);
this.ctx = ctx;
this.env = env;
this.bindings = bindings;
this.setAlarm(Date.now());
}
export type ContainerBindingMap = Record<string, DurableObjectNamespace>;
export class CommonContainerManager extends DurableObject<Env> {
constructor(
ctx: DurableObjectState,
env: Env,
bindings: ContainerBindingMap,
) {
super(ctx, env);
this.ctx = ctx;
this.env = env;
this.bindings = bindings;
this.setAlarm(Date.now());
}
And I would call it like:
export class MyContainerManager extends CommonContainerManager {
constructor(ctx: DurableObjectState, env: Env) {
// The bindingMap we pass into CommonContainerManagers constructor here
// is what allows us to target a specific binding based on the users
// requested size.
const bindingMap: ContainerBindingMap = {
dev: env.CONTAINER_DEV,
basic: env.CONTAINER_BASIC,
standard: env.CONTAINER_STANDARD,
};
super(ctx, env, bindingMap);
this.ctx = ctx;
this.env = env;
}
export class MyContainerManager extends CommonContainerManager {
constructor(ctx: DurableObjectState, env: Env) {
// The bindingMap we pass into CommonContainerManagers constructor here
// is what allows us to target a specific binding based on the users
// requested size.
const bindingMap: ContainerBindingMap = {
dev: env.CONTAINER_DEV,
basic: env.CONTAINER_BASIC,
standard: env.CONTAINER_STANDARD,
};
super(ctx, env, bindingMap);
this.ctx = ctx;
this.env = env;
}
Where CONTAINER_{DEV/BASIC/STANDARD} were defined in the wrangler.jsonc as durable_objects and under migrations as new_sqlite_classes. Then the function analogous to your findContainer() was part of the manager DO and was hidden from the worker with a call like:
requestContainer(name: string, request: Request)
requestContainer(name: string, request: Request)
But internally the manager was just doing const binding = this.bindings[name]
Phatso
PhatsoOP2mo ago
ooh, neat, I like that 👀

Did you find this page helpful?