Skip to content

Conversation

cprecioso
Copy link
Member

@cprecioso cprecioso commented Sep 22, 2025

Adds an optional --db-image argument to the run-wasp-app command, allowing us to specify a custom Docker image for the database.

This is part of #3174. We're adding ask-the-documents to the repo, which needs a specific PostgreSQL extension. This way we can more easily test the app.

Changes

  • Modified run-wasp-app to accept and use custom Docker images
  • Added explanation to README

Note

🤖 Generated with Claude Code
and 👨🏻 reviewed and edited by a person

Allows users to specify a custom Docker image when starting the development database.
Usage: wasp start db --image postgres:15

This enables developers to test with different PostgreSQL versions or custom database images.
- Note default postgres image in help text
- Add error handling for unrecognized arguments
- Use fromMaybe to simplify image selection logic
- Add comprehensive documentation in CLI and database docs
- Replace manual argument parsing with Options.Applicative parser
- Move parser to top level as requested
- Add note about PostgreSQL extensions use case in documentation
- Remove redundant CLI documentation as requested
- Change StartDbArgs from newtype to data type
- Move startDbArgsParser below the main start function
- Parse arguments as the first operation in start function
@cprecioso cprecioso self-assigned this Sep 22, 2025
@cprecioso cprecioso force-pushed the image-arg-for-wasp-app-runner branch 2 times, most recently from d61b39e to 4a541ed Compare September 22, 2025 13:18
@cprecioso cprecioso force-pushed the image-arg-for-wasp-app-runner branch from 4a541ed to cd24309 Compare September 22, 2025 13:22
@cprecioso cprecioso changed the title feat: add --image arg to wasp start db command Add --db-image to wasp-app-runner Sep 22, 2025
@cprecioso cprecioso changed the title Add --db-image to wasp-app-runner Add --db-image argument to wasp-app-runner Sep 22, 2025
@cprecioso cprecioso changed the base branch from main to db-image-arg September 22, 2025 14:08
Copy link

cloudflare-workers-and-pages bot commented Sep 22, 2025

Deploying wasp-docs-on-main with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9d09644
Status: ✅  Deploy successful!
Preview URL: https://faf2c3f8.wasp-docs-on-main.pages.dev
Branch Preview URL: https://image-arg-for-wasp-app-runne.wasp-docs-on-main.pages.dev

View logs

@cprecioso cprecioso marked this pull request as ready for review September 22, 2025 14:14
Copy link
Contributor

@FranjoMindek FranjoMindek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can make the db-image configuration global rather than local.
Very straightforward and clean RP otherwise.

Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a concern similar to what I've voiced in #3182 (review).

Details here: #3183 (comment)

mode: Mode;
pathToApp: PathToApp;
waspCliCmd: WaspCliCmd;
dbImage?: string;
Copy link
Contributor

@sodic sodic Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this code has the same problem I talked about in my review of #3182 (review).

The "optionality" of dbImage is stretching itself too deep into the code. The default value should be handled as soon as possible. Most of the code shouldn't even know whether it's working with a default value or a custom value.

Think of it like this:

If we want to change the default value, the change should be in a single, obvious place. And the place should be the same for all flags/default values. We shouldn't have to dig all the way down to specific functions to change specific values.

Btw, am I missing something here? Since all other values are handled this way already and only the new one isn't, it's possible I'm not seeing what you're seeing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it on purpose really, because of two reasons:

  • IMO semantically, it doesn't make sense to handle the default value in the CLI parsing level. Imagine we add support for a MySQL DB, then the default value of --db-image would actually be dynamically determined through the flow of the program, since we wouldn't want to set postgres image then, but a mysql one.

    You can see this in the current code where the sqlite and postgres drivers have the same input parameters, so if I'm marking the dbImage as required and giving it a value at the top-level, I'm at some point passing dbImage: postgres into the sqlite driver. Of course (1) that's a no-op, and (2) nothing a refactor can't fix. But it points to this issue I'm talking about.

  • The fact that we are using a default value or not is sometimes relevant. For example, in the Railway PR, we need to set some variables if we're not using the default image. Of course, here's not an issue, but in my mind they're both working at the same level of abstraction.

But yeah this is me overvaluing a "correct" implementation ready to get extended, and undervaluing the straightforward-ness to handle this at the top level.

Wdyt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But yeah this is me overvaluing a "correct" implementation ready to get extended, and undervaluing the straightforward-ness to handle this at the top level.

This is not clear to me hm, why do you think you are overvaluing a correct implementation vs simpler implementation? I think @sodic was also pushing for the most correct implementation.

I am not sure if I am getting the whole thing right. I think what you are saying is, we can, in case when CLI arg is not provided, set it to the default value right at the top, or we can recognize it is not set (set it to Nothing), and then when we want to do something with it, we can at that moment decide how to handle that and grab that default value from somewhere.

I think @sodic suggested the first approach because it means you have that logic of "what do I do when it is empty, aha I set it to default" at only one place in the code, avoiding duplication and desync in the future.

We could alternatively have one central function which does this ("give me the value of db docker image") and then leave the value of image to be Nothing, and then expect other parts of code to call that function. This gives the rest of the code more knowledge, because there is extra state of Nothing that they can react on (if relevant), but it does leave some extra space for a mistake, since they might not call that function as intended and do something else, by accident.

So first approach is simpler, and less code, but one "state" less, therefore less "knowledge" in the system.
Second approach is more complex, more code, more space for future mistakes, but one "state" more and more "knowledge" in the system.

I would say, unless it is clear that more knowledge is useful, or that it feels semantically correct (I know this sounds vague, but sometimes it just makes sense, I can't say right now because I am not immersed enough into this code), let's go for simpler option (approach one).

Btw I didn't get the point with mysql and postgres, so my bad if there was something there that would inform this discussion further. I do like the idea of tying up the default docker image with the default db system at the type (or some hm) level, if that is what you are suggesting.
Aaaaah I get what you mean now. If user set db.system to mysql, then we wouldn't set the value of --db-image to postgres, but to mysql docker image. Ok, true, that makes sense to me. But we can just say in the CLI docs that we will set it to default image for their chosen db system. We could even dynamically determine which one is that, as you said :D.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you think you are overvaluing a correct implementation vs simpler implementation?

oof not calling the other thing "incorrect", sorry! just that IMO with the constraints i had in mind, that was the most semantically accurate.

or that it feels semantically correct (I know this sounds vague, but sometimes it just makes sense, I can't say right now because I am not immersed enough into this code),

yeah this, it felt correct

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I don't have anything else to say here so I will let the two of you figure it out, as I think @sodic has a better grasp of the code than me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO semantically, it doesn't make sense to handle the default value in the CLI parsing level. Imagine we add support for a MySQL DB, then the default value of --db-image would actually be dynamically determined through the flow of the program, since we wouldn't want to set postgres image then, but a mysql one.

Me personally, I would still do it at the start of the program.
If above happens we will most likely need to parse the AppSpec to know what to do with the application (it also includes the prisma schema).

So the CLI and the AppSpec would be the two configuration inputs into the program.
As is usually with these programs, CLI would override the AppSpec provided values.

If so I would declare default values as part of the AppSpec parsing process.

That way we:
We have a single source of truth for configuration.
The information is known at the start of the program. If we ever need it at some other place we don't have to refactor the code to move the default value declaration to shared parent.
wasp-app-runner core/driver has to knew less about how Wasp applications work.

My thinking is if this operation can be overriden at the top, why not just default value it at the top?
The program must work with values defined at the top anyway.
This way we don't have to care about undefined values in the core/driver itself.

You can see this in the current code where the sqlite and postgres drivers have the same input parameters, so if I'm marking the dbImage as required and giving it a value at the top-level, I'm at some point passing dbImage: postgres into the sqlite driver. Of course (1) that's a no-op, and (2) nothing a refactor can't fix. But it points to this issue I'm talking about.

We shouldn't enforce the same interface if the needs are not the same.
I would say this was a mistake on our part.

We will have some branching logic no matter the solution, but I'm sure we could abstract some docker behavior when we support multiple databases if we do it this way.

The fact that we are using a default value or not is sometimes relevant. For example, in the Railway PR, we need to set some variables if we're not using the default image. Of course, here's not an issue, but in my mind they're both working at the same level of abstraction.

Yes, if we don't mind if it's default or not, let's just combine all the input data at the top?

Copy link
Contributor

@sodic sodic Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can see this in the current code where the sqlite and postgres drivers have the same input parameters, so if I'm marking the dbImage as required and giving it a value at the top-level, I'm at some point passing dbImage: postgres into the sqlite driver.

This is a great observation, but my conclusion is different :)

The core problem is that our current implementation has a "type error." It's not a real type error, but our groupings and parameters are set up in a way that allows invalid states.
Effective TS explains this in Item 29: Prefer Types That Always Represent Valid States.

Specifically, the parameter combination of dbType: DbType, dbImage?: string does not properly model our constraints. We lose information and are forced to think about a state which shouldn't exist, dbType: SQLite, dbImage: somethingOtherThanUndefined.

That is the root issue we should address. I think we should address it as high up as possible and propagate this information through the type system.

This problem likely affects the CLI too - we shouldn't really allow users to set --db-image if they're using dev mode. We should instead tell them something like "You can only use --db-image in build mode." If I'm not mistaken, the current implementation will just ignore the --db-image argument when used in dev mode.

Imagine we add support for a MySQL DB, then the default value of --db-image would actually be dynamically determined

@FranjoMindek did a great job explaining this point.

Wasp app runner currently asssumes that "build" means "Use a postgres database" and "dev" means "Use an SQLite database."

If we do add MySQL, we'll have to break this assumption and pass an extra input to the runner - either the full app specification like Franjo suggested, or at least an argument specifying the database. In any case, the app runner will get more info and still be able to decide which default to use at the top-level.

Finally, notice how "dev" doesn't have to mean "Use an SQLite database." Apps in dev mode can also use Postgres and, if that becomes necessary, we will again want to communicate this through argument codependencies and the type system.

For example, let's assume we add MySQL to Wasp. Let's also assume we want to work with real databases in development, so we add a --db-type flag.

run-wasp-app dev should then allow the --db-type to be either SQLite, MySql, or PostgreSQL.

  • If --db-type is SQLite, then we don't allow a --db-image flag.
  • If --db-type is MySQL or PostgreSQL, then we allow a --db-image flag and choose an appropriate default if one isn't provided. We could even check the image compatibility with the database if we decide it's useful (but again, near the top level).

These constraints and codependencies between arguments should then again be properly modelled by the type system.

The fact that we are using a default value or not is sometimes relevant. For example, in the Railway PR, we need to set some variables if we're not using the default image. Of course, here's not an issue, but in my mind they're both working at the same level of abstraction.

I don't think those are related. Wasp app runner needs the image to start a docker container. It's not doing anything fancy and I'd expect it to stay that way.

Finally, @FranjoMindek said:

We shouldn't enforce the same interface if the needs are not the same.
I would say this was a mistake on our part.

Yes, I agree. We should drop the SetupDbFn type. It doesn't provide anything useful and just forces our drivers to have the same inputs. I think the correct way to handle this would be doing something like:

// This would be parsed somewhere up top
// I'm making up type names and don't have this fleshed out, but you get the idea
type DbConfig = { dbType: SQLite } | { dbType: PostgreSQL, appName: string, pathToApp: string, dbImage: string }

export function setupDb(dbConfig: DbConfig): DbSetupResult {
  // ...
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I see the point. So as far as I can see, due to the needs of the code right now, I'm just pushing the default value to as top as I can get it (argument parser). In the future if we add more DB systems, we can then try and model the types in the way of "Making illegal states invalid".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, that's the best thing to do right now, I agree.

Copy link
Contributor

@sodic sodic Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can then try and model the types in the way of "Making illegal states invalid".

@cprecioso Do you mind creating an issue for this? 😬

Also, I still think we should warn the caller that this option can't be used in dev mode instead of silently ignoring it.

Copy link
Member

@Martinsos Martinsos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Others covered main stuff well, I just poped a couple of extra comments. I will remain in the role of weak reviewer here and just comment, I won'd be doing approving.

@cprecioso cprecioso force-pushed the image-arg-for-wasp-app-runner branch from 76b79c0 to 3d8fd82 Compare September 26, 2025 08:28
Base automatically changed from db-image-arg to main September 26, 2025 09:29
@cprecioso
Copy link
Member Author

Ready for re-review @sodic @FranjoMindek @Martinsos

Copy link
Contributor

@FranjoMindek FranjoMindek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my side it's okay.
I didn't check comments left by others.

Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff, I'd just like us to inform the user about the inability to use --db-image in dev mode, both in the docs and when using the CLI.

DATABASE_URL=postgresql://postgres:devpass@localhost:5432/postgres
```

#### Custom database image
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs should mention that this can't be used in dev mode too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did you get the impression that it can't be used on dev mode? It can.

Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to go!

Comment on lines +57 to +63
if (dbImageArg && dbType !== DbType.Postgres) {
logger.error(
`The --db-image option is only valid when using PostgreSQL as the database.`,
);
process.exit(1);
}
const dbImage = dbImageArg ?? defaultPostgresDbImage;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame we had to push this all the way down here but I see no other way :/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep 🫠

@cprecioso cprecioso merged commit dec52b8 into main Oct 1, 2025
2 checks passed
@cprecioso cprecioso deleted the image-arg-for-wasp-app-runner branch October 1, 2025 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants