mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-26 22:36:01 +03:00
Adds support for Dockerfile customization (#732)
This commit is contained in:
parent
a84f3547fa
commit
8fc6fd1836
@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## v0.x.x.x (TBD)
|
||||
|
||||
### [NEW FEATURE] Dockerfile customization
|
||||
|
||||
You can now customize the default Wasp Dockerfile by either extending/replacing our build stages or using your own custom logic. To make use of this feature, simply add a Dockerfile to the root of your project and it will be appended to the bottom of the existing Wasp Dockerfile.
|
||||
|
||||
## v0.6.0.0 (2022/09/29)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
@ -19,6 +19,7 @@ import Wasp.Cli.Command.CreateNewProject (createNewProject)
|
||||
import Wasp.Cli.Command.Db (runDbCommand, studio)
|
||||
import qualified Wasp.Cli.Command.Db.Migrate as Command.Db.Migrate
|
||||
import Wasp.Cli.Command.Deps (deps)
|
||||
import Wasp.Cli.Command.Dockerfile (printDockerfile)
|
||||
import Wasp.Cli.Command.Info (info)
|
||||
import Wasp.Cli.Command.Start (start)
|
||||
import qualified Wasp.Cli.Command.Telemetry as Telemetry
|
||||
@ -40,6 +41,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
|
||||
["build"] -> Command.Call.Build
|
||||
["telemetry"] -> Command.Call.Telemetry
|
||||
["deps"] -> Command.Call.Deps
|
||||
["dockerfile"] -> Command.Call.Dockerfile
|
||||
["info"] -> Command.Call.Info
|
||||
["completion"] -> Command.Call.PrintBashCompletionInstruction
|
||||
["completion:generate"] -> Command.Call.GenerateBashCompletionScript
|
||||
@ -59,6 +61,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do
|
||||
Command.Call.Build -> runCommand build
|
||||
Command.Call.Telemetry -> runCommand Telemetry.telemetry
|
||||
Command.Call.Deps -> runCommand deps
|
||||
Command.Call.Dockerfile -> runCommand printDockerfile
|
||||
Command.Call.Info -> runCommand info
|
||||
Command.Call.PrintBashCompletionInstruction -> runCommand printBashCompletionInstruction
|
||||
Command.Call.GenerateBashCompletionScript -> runCommand generateBashCompletionScript
|
||||
@ -96,6 +99,7 @@ printUsage =
|
||||
cmd " build Generates full web app code, ready for deployment. Use when deploying or ejecting.",
|
||||
cmd " telemetry Prints telemetry status.",
|
||||
cmd " deps Prints the dependencies that Wasp uses in your project.",
|
||||
cmd " dockerfile Prints the contents of the Wasp generated Dockerfile.",
|
||||
cmd " info Prints basic information about current Wasp project.",
|
||||
"",
|
||||
title "EXAMPLES",
|
||||
|
@ -10,6 +10,7 @@ data Call
|
||||
| Version
|
||||
| Telemetry
|
||||
| Deps
|
||||
| Dockerfile
|
||||
| Info
|
||||
| PrintBashCompletionInstruction
|
||||
| GenerateBashCompletionScript
|
||||
|
21
waspc/cli/src/Wasp/Cli/Command/Dockerfile.hs
Normal file
21
waspc/cli/src/Wasp/Cli/Command/Dockerfile.hs
Normal file
@ -0,0 +1,21 @@
|
||||
module Wasp.Cli.Command.Dockerfile
|
||||
( printDockerfile,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Monad.Except (throwError)
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.Text.IO as T.IO
|
||||
import Wasp.Cli.Command (Command, CommandError (..))
|
||||
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd)
|
||||
import Wasp.Cli.Command.Compile (defaultCompileOptions)
|
||||
import Wasp.Lib (compileAndRenderDockerfile)
|
||||
|
||||
printDockerfile :: Command ()
|
||||
printDockerfile = do
|
||||
waspProjectDir <- findWaspProjectRootDirFromCwd
|
||||
dockerfileContentOrCompileErrors <- liftIO $ compileAndRenderDockerfile waspProjectDir (defaultCompileOptions waspProjectDir)
|
||||
either
|
||||
(throwError . CommandError "Displaying Dockerfile failed due to a compilation error in your Wasp project" . unwords)
|
||||
(liftIO . T.IO.putStrLn)
|
||||
dockerfileContentOrCompileErrors
|
@ -33,3 +33,7 @@ COPY db/ ./db/
|
||||
EXPOSE ${PORT}
|
||||
WORKDIR /app/server
|
||||
ENTRYPOINT ["npm", "run", "start-production"]
|
||||
|
||||
|
||||
# Any user-defined Dockerfile contents will be appended below.
|
||||
{=& userDockerfile =}
|
||||
|
@ -11,7 +11,7 @@
|
||||
"file",
|
||||
"Dockerfile"
|
||||
],
|
||||
"faae4a6f87557e624d9c7631ec6f3ed20e31115f2c177bfe19b3f52d163d86e9"
|
||||
"102dded28312bbe81553d71eb878ed4f7766fda51503e08e75ffd9931ff1a8bb"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -25,3 +25,7 @@ COPY db/ ./db/
|
||||
EXPOSE ${PORT}
|
||||
WORKDIR /app/server
|
||||
ENTRYPOINT ["npm", "run", "start-production"]
|
||||
|
||||
|
||||
# Any user-defined Dockerfile contents will be appended below.
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
"file",
|
||||
"Dockerfile"
|
||||
],
|
||||
"faae4a6f87557e624d9c7631ec6f3ed20e31115f2c177bfe19b3f52d163d86e9"
|
||||
"102dded28312bbe81553d71eb878ed4f7766fda51503e08e75ffd9931ff1a8bb"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -25,3 +25,7 @@ COPY db/ ./db/
|
||||
EXPOSE ${PORT}
|
||||
WORKDIR /app/server
|
||||
ENTRYPOINT ["npm", "run", "start-production"]
|
||||
|
||||
|
||||
# Any user-defined Dockerfile contents will be appended below.
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
"file",
|
||||
"Dockerfile"
|
||||
],
|
||||
"faae4a6f87557e624d9c7631ec6f3ed20e31115f2c177bfe19b3f52d163d86e9"
|
||||
"102dded28312bbe81553d71eb878ed4f7766fda51503e08e75ffd9931ff1a8bb"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -25,3 +25,7 @@ COPY db/ ./db/
|
||||
EXPOSE ${PORT}
|
||||
WORKDIR /app/server
|
||||
ENTRYPOINT ["npm", "run", "start-production"]
|
||||
|
||||
|
||||
# Any user-defined Dockerfile contents will be appended below.
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
"file",
|
||||
"Dockerfile"
|
||||
],
|
||||
"f94ec7a7e7db084cdda6a0860d68693252da43d7ddd28ba990222ea0831c5467"
|
||||
"0134f44513be86f913897e47699114aaa1f1b497152cabe93755b992957b95e6"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -27,3 +27,7 @@ COPY db/ ./db/
|
||||
EXPOSE ${PORT}
|
||||
WORKDIR /app/server
|
||||
ENTRYPOINT ["npm", "run", "start-production"]
|
||||
|
||||
|
||||
# Any user-defined Dockerfile contents will be appended below.
|
||||
|
||||
|
1
waspc/examples/todoApp/Dockerfile
Normal file
1
waspc/examples/todoApp/Dockerfile
Normal file
@ -0,0 +1 @@
|
||||
## HELLO!
|
@ -17,6 +17,7 @@ where
|
||||
|
||||
import Data.List (find)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import StrongPath (Abs, Dir, File', Path')
|
||||
import Wasp.AppSpec.Action (Action)
|
||||
import Wasp.AppSpec.Core.Decl (Decl, IsDecl, takeDecls)
|
||||
@ -50,7 +51,9 @@ data AppSpec = AppSpec
|
||||
dotEnvClientFile :: Maybe (Path' Abs File'),
|
||||
-- | If true, it means project is being compiled for production/deployment -> it is being "built".
|
||||
-- If false, it means project is being compiled for development purposes (e.g. "wasp start").
|
||||
isBuild :: Bool
|
||||
isBuild :: Bool,
|
||||
-- | The contents of the optional user Dockerfile found in the root of the wasp project source.
|
||||
userDockerfileContents :: Maybe Text
|
||||
}
|
||||
|
||||
-- TODO: Make this return "Named" declarations?
|
||||
|
@ -2,19 +2,25 @@
|
||||
|
||||
module Wasp.Generator.DockerGenerator
|
||||
( genDockerFiles,
|
||||
genDockerfile,
|
||||
compileAndRenderDockerfile,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import StrongPath (File', Path', Rel, relfile)
|
||||
import Wasp.AppSpec (AppSpec)
|
||||
import qualified Wasp.AppSpec as AS
|
||||
import qualified Wasp.AppSpec.Entity as AS.Entity
|
||||
import Wasp.Generator.Common (ProjectRootDir, latestMajorNodeVersion)
|
||||
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import Wasp.Generator.FileDraft (FileDraft (..), createTemplateFileDraft)
|
||||
import qualified Wasp.Generator.FileDraft.TemplateFileDraft as TmplFD
|
||||
import Wasp.Generator.Monad (Generator, GeneratorError, runGenerator)
|
||||
import Wasp.Generator.ServerGenerator (areServerPatchesUsed)
|
||||
import Wasp.Generator.Templates (TemplatesDir)
|
||||
import Wasp.Generator.Templates (TemplatesDir, compileAndRenderTemplate)
|
||||
import qualified Wasp.SemanticVersion as SV
|
||||
|
||||
genDockerFiles :: AppSpec -> Generator [FileDraft]
|
||||
@ -32,7 +38,8 @@ genDockerfile spec = do
|
||||
object
|
||||
[ "usingPrisma" .= not (null $ AS.getDecls @AS.Entity.Entity spec),
|
||||
"nodeMajorVersion" .= show (SV.major latestMajorNodeVersion),
|
||||
"usingServerPatches" .= usingServerPatches
|
||||
"usingServerPatches" .= usingServerPatches,
|
||||
"userDockerfile" .= fromMaybe "" (AS.userDockerfileContents spec)
|
||||
]
|
||||
)
|
||||
|
||||
@ -43,3 +50,14 @@ genDockerignore _ =
|
||||
([relfile|.dockerignore|] :: Path' (Rel ProjectRootDir) File')
|
||||
([relfile|dockerignore|] :: Path' (Rel TemplatesDir) File')
|
||||
Nothing
|
||||
|
||||
-- | Helper to return what the Dockerfile content will be based on the AppSpec.
|
||||
compileAndRenderDockerfile :: AppSpec -> IO (Either (NonEmpty GeneratorError) Text)
|
||||
compileAndRenderDockerfile spec = do
|
||||
let (_, generatorResult) = runGenerator $ genDockerfile spec
|
||||
case generatorResult of
|
||||
Left generatorErrors -> return $ Left generatorErrors
|
||||
Right (FileDraftTemplateFd draft) -> do
|
||||
content <- compileAndRenderTemplate (TmplFD._srcPathInTmplDir draft) (fromMaybe (object []) (TmplFD._tmplData draft))
|
||||
return $ Right content
|
||||
Right _ -> error "Attempted to display Dockerfile, but it was not a Template FileDraft!"
|
||||
|
@ -4,11 +4,16 @@ module Wasp.Lib
|
||||
ProjectRootDir,
|
||||
findWaspFile,
|
||||
analyzeWaspProject,
|
||||
compileAndRenderDockerfile,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Arrow (left)
|
||||
import Control.Monad.Extra (whenMaybeM)
|
||||
import Data.List (find, isSuffixOf)
|
||||
import Data.List.NonEmpty (NonEmpty, fromList, toList)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text.IO as T.IO
|
||||
import StrongPath (Abs, Dir, File', Path', relfile)
|
||||
import qualified StrongPath as SP
|
||||
import System.Directory (doesDirectoryExist, doesFileExist)
|
||||
@ -23,6 +28,7 @@ import Wasp.Error (showCompilerErrorForTerminal)
|
||||
import qualified Wasp.ExternalCode as ExternalCode
|
||||
import qualified Wasp.Generator as Generator
|
||||
import Wasp.Generator.Common (ProjectRootDir)
|
||||
import qualified Wasp.Generator.DockerGenerator as DockerGenerator
|
||||
import Wasp.Generator.ServerGenerator.Common (dotEnvServer)
|
||||
import Wasp.Generator.WebAppGenerator.Common (dotEnvClient)
|
||||
import qualified Wasp.Util.IO as Util.IO
|
||||
@ -74,6 +80,7 @@ analyzeWaspProject waspDir options = do
|
||||
maybeDotEnvServerFile <- findDotEnvServer waspDir
|
||||
maybeDotEnvClientFile <- findDotEnvClient waspDir
|
||||
maybeMigrationsDir <- findMigrationsDir waspDir
|
||||
maybeUserDockerfileContents <- loadUserDockerfileContents waspDir
|
||||
return $
|
||||
Right
|
||||
AS.AppSpec
|
||||
@ -83,7 +90,8 @@ analyzeWaspProject waspDir options = do
|
||||
AS.migrationsDir = maybeMigrationsDir,
|
||||
AS.dotEnvServerFile = maybeDotEnvServerFile,
|
||||
AS.dotEnvClientFile = maybeDotEnvClientFile,
|
||||
AS.isBuild = CompileOptions.isBuild options
|
||||
AS.isBuild = CompileOptions.isBuild options,
|
||||
AS.userDockerfileContents = maybeUserDockerfileContents
|
||||
}
|
||||
analyzerWarnings <- warnIfDotEnvPresent waspDir
|
||||
return (analyzerWarnings, appSpecOrAnalyzerErrors)
|
||||
@ -130,3 +138,17 @@ findMigrationsDir waspDir = do
|
||||
let migrationsAbsPath = waspDir SP.</> dbMigrationsDirInWaspProjectDir
|
||||
migrationsExists <- doesDirectoryExist $ SP.fromAbsDir migrationsAbsPath
|
||||
return $ if migrationsExists then Just migrationsAbsPath else Nothing
|
||||
|
||||
loadUserDockerfileContents :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe Text)
|
||||
loadUserDockerfileContents waspDir = do
|
||||
let dockerfileAbsPath = SP.toFilePath $ waspDir SP.</> [relfile|Dockerfile|]
|
||||
whenMaybeM (doesFileExist dockerfileAbsPath) $ T.IO.readFile dockerfileAbsPath
|
||||
|
||||
compileAndRenderDockerfile :: Path' Abs (Dir WaspProjectDir) -> CompileOptions -> IO (Either [CompileError] Text)
|
||||
compileAndRenderDockerfile waspDir compileOptions = do
|
||||
(_, appSpecOrAnalyzerErrors) <- analyzeWaspProject waspDir compileOptions
|
||||
case appSpecOrAnalyzerErrors of
|
||||
Left errors -> return . Left . toList $ errors
|
||||
Right appSpec -> do
|
||||
dockerfileOrGeneratorErrors <- DockerGenerator.compileAndRenderDockerfile appSpec
|
||||
return $ left (map show . toList) dockerfileOrGeneratorErrors
|
||||
|
@ -115,6 +115,7 @@ library
|
||||
-- 'array' is used by code generated by Alex for src/Analyzer/Parser/Lexer.x
|
||||
, array ^>= 0.5.4
|
||||
, deepseq ^>= 1.4.4
|
||||
, extra ^>= 1.7.10
|
||||
other-modules: Paths_waspc
|
||||
exposed-modules:
|
||||
FilePath.Extra
|
||||
@ -308,6 +309,7 @@ library cli-lib
|
||||
, path
|
||||
, path-io
|
||||
, strong-path
|
||||
, text
|
||||
, utf8-string
|
||||
, uuid
|
||||
, waspc
|
||||
@ -325,6 +327,7 @@ library cli-lib
|
||||
Wasp.Cli.Command.Db
|
||||
Wasp.Cli.Command.Db.Migrate
|
||||
Wasp.Cli.Command.Deps
|
||||
Wasp.Cli.Command.Dockerfile
|
||||
Wasp.Cli.Command.Info
|
||||
Wasp.Cli.Command.Start
|
||||
Wasp.Cli.Command.Telemetry
|
||||
|
@ -143,3 +143,15 @@ and carefully follow their instructions (i.e. do you want to create a new app or
|
||||
That is it!
|
||||
|
||||
NOTE: Make sure you set this URL as the `WASP_WEB_CLIENT_URL` environment variable in Heroku.
|
||||
|
||||
## Customizing the Dockerfile
|
||||
By default, Wasp will generate a multi-stage Dockerfile that is capable of building an image with your Wasp-generated server code and running it, along with any pending migrations, as in the deployment scenario above. If you need to customize this Dockerfile, you may do so by adding a Dockerfile to your project root directory. If present, Wasp will append the contents of this file to the _bottom_ of our default Dockerfile.
|
||||
|
||||
Since the last definition in a Dockerfile wins, you can override or continue from any existing build stages. You could also choose not to use any of our build stages and have your own custom Dockerfile used as-is. A few notes are in order:
|
||||
- if you override an intermediate build stage, no later build stages will be used unless you reproduce them below
|
||||
- the contents of the Dockerfile are dynamic, based on the features you use, and may change in future releases as well, so please verify the contents have not changed from time to time
|
||||
- be sure to supply an `ENTRYPOINT` in your final build stage or it will not have any effect
|
||||
|
||||
To see what your project's (potentially combined) Dockerfile will look like, run: `wasp dockerfile`
|
||||
|
||||
Here are the official docker docs on [multi-stage builds](https://docs.docker.com/build/building/multi-stage/). Please join our Discord if you have any questions, or if the customization hook provided here is not sufficient for your needs!
|
||||
|
Loading…
Reference in New Issue
Block a user