[{"data":1,"prerenderedAt":726},["ShallowReactive",2],{"docs-nav":3,"docs:\u002Fdocs\u002Ftutorials\u002Ffirst-local-app":139},[4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130,133,136],{"path":5,"title":6},"\u002Fdocs\u002Faccess-gating","Protect a local route",{"path":8,"title":9},"\u002Fdocs\u002Farchitecture\u002Fsecurity-and-trust","Security and trust",{"path":11,"title":12},"\u002Fdocs\u002Fconcepts\u002Fbuilds-and-deployments","Build and deploy",{"path":14,"title":15},"\u002Fdocs\u002Fconcepts\u002Fconfiguration-ownership","Where configuration belongs",{"path":17,"title":18},"\u002Fdocs\u002Fconcepts\u002Fenvironments-and-secrets","Environment values and secrets",{"path":20,"title":21},"\u002Fdocs\u002Fconcepts\u002Fmental-model","How projects fit together",{"path":23,"title":24},"\u002Fdocs\u002Fconcepts\u002Fplans-and-apply","Plans and apply",{"path":26,"title":27},"\u002Fdocs\u002Fconcepts\u002Ftargets-and-runtimes","Choose a target and runtime",{"path":29,"title":30},"\u002Fdocs\u002Fcontribute\u002Freleases","Publish a release",{"path":32,"title":33},"\u002Fdocs\u002Fdevelopment-mounts","Development mounts",{"path":35,"title":36},"\u002Fdocs\u002Fgetting-started","Getting started",{"path":38,"title":39},"\u002Fdocs\u002Fhelp\u002Ffaq","FAQ",{"path":41,"title":42},"\u002Fdocs\u002Fhelp\u002Ftroubleshooting","Troubleshooting",{"path":44,"title":45},"\u002Fdocs\u002Fhow-to\u002Fdeploy-production","Prepare a deployment environment",{"path":47,"title":48},"\u002Fdocs\u002Fhow-to\u002Fswarm-cluster","Run on a multi-node Swarm",{"path":50,"title":51},"\u002Fdocs","mm documentation",{"path":53,"title":54},"\u002Fdocs\u002Fjobs-and-schedules","Jobs and schedules",{"path":56,"title":57},"\u002Fdocs\u002Flocal-dashboard","Local dashboard",{"path":59,"title":60},"\u002Fdocs\u002Flocal-https","Local HTTPS",{"path":62,"title":63},"\u002Fdocs\u002Flocal-services","Local services",{"path":65,"title":66},"\u002Fdocs\u002Fmcp","Let an AI agent work on your project",{"path":68,"title":69},"\u002Fdocs\u002Fmulti-project-hosts","Run multiple projects on one host",{"path":71,"title":72},"\u002Fdocs\u002Fonboarding","Set up a project",{"path":74,"title":75},"\u002Fdocs\u002Fopenbao","Use OpenBao",{"path":77,"title":78},"\u002Fdocs\u002Fproject\u002Fcontributing","Contributing",{"path":80,"title":81},"\u002Fdocs\u002Fproject\u002Flicense","License",{"path":83,"title":84},"\u002Fdocs\u002Fproject\u002Fsecurity","Security",{"path":86,"title":87},"\u002Fdocs\u002Freference\u002Fcapability-matrix","Supported features and platforms",{"path":89,"title":90},"\u002Fdocs\u002Freference\u002Fcli","CLI command reference",{"path":92,"title":93},"\u002Fdocs\u002Freference\u002Fconfiguration","Configuration",{"path":95,"title":96},"\u002Fdocs\u002Freference\u002Fenvironment-variables","Environment variables",{"path":98,"title":99},"\u002Fdocs\u002Freference\u002Ferrors-and-exit-codes","Errors and exit codes",{"path":101,"title":102},"\u002Fdocs\u002Freference\u002Fknown-issues","Known limitations",{"path":104,"title":105},"\u002Fdocs\u002Fremote-access","Manage remote access",{"path":107,"title":108},"\u002Fdocs\u002Fremote-daemon","Set up a remote host",{"path":110,"title":111},"\u002Fdocs\u002Fruntime-observability","Status, logs, and commands",{"path":113,"title":114},"\u002Fdocs\u002Fsecret-delivery","Pass secrets to your app",{"path":116,"title":117},"\u002Fdocs\u002Fsecret-providers","Choose a secret store",{"path":119,"title":120},"\u002Fdocs\u002Fstart\u002Finstall","Install mm",{"path":122,"title":123},"\u002Fdocs\u002Fterminal-ui","Terminal UI",{"path":125,"title":126},"\u002Fdocs\u002Ftutorials\u002Fdotnet-web-api",".NET web API example",{"path":128,"title":129},"\u002Fdocs\u002Ftutorials\u002Ffirst-local-app","First local app",{"path":131,"title":132},"\u002Fdocs\u002Ftutorials\u002Flaravel-rust","Laravel and Rust example",{"path":134,"title":135},"\u002Fdocs\u002Ftutorials\u002Fnode-fullstack","Node full stack example",{"path":137,"title":138},"\u002Fdocs\u002Fvault","Use HashiCorp Vault",{"id":140,"title":129,"body":141,"description":719,"extension":720,"meta":721,"navigation":722,"path":128,"seo":723,"stem":724,"__hash__":725},"docs\u002Fdocs\u002Ftutorials\u002Ffirst-local-app.md",{"type":142,"value":143,"toc":710},"minimark",[144,153,158,161,182,185,189,192,261,267,274,284,288,299,311,314,340,354,358,408,415,418,425,463,466,505,508,512,515,540,547,576,608,616,620,623,639,642,691,706],[145,146,147,148,152],"p",{},"This walkthrough runs ",[149,150,151],"code",{},"examples\u002Fnode-fullstack",": a frontend, API, and MongoDB.\nYou will build the application images, start the services, and check the API\nover HTTPS.",[154,155,157],"h2",{"id":156},"before-you-start","Before you start",[145,159,160],{},"You need:",[162,163,164,173,176,179],"ul",{},[165,166,167,172],"li",{},[168,169,171],"a",{"href":170},"..\u002Fstart\u002Finstall","mm installed",".",[165,174,175],{},"A supported Linux systemd machine with HAProxy, Docker Compose, and Docker Buildx.",[165,177,178],{},"A full Git checkout of this repository, including tags.",[165,180,181],{},"Two container repositories you can push to, with Docker already logged in.",[145,183,184],{},"This example publishes images even for local deployment. It cannot deploy\nfirst-party images from a local-only build. Use a development machine.",[154,186,188],{"id":187},"set-up-the-workstation","Set up the workstation",[145,190,191],{},"From the repository root:",[193,194,199],"pre",{"className":195,"code":196,"language":197,"meta":198,"style":198},"language-sh shiki shiki-themes mm","cd examples\u002Fnode-fullstack\nmm --runtime docker-compose dev setup --services\nmm target preference set-runtime docker-compose\nmm doctor\n","sh","",[149,200,201,214,236,253],{"__ignoreMap":198},[202,203,206,210],"span",{"class":204,"line":205},"line",1,[202,207,209],{"class":208},"svrjQ","cd",[202,211,213],{"class":212},"sv8ni"," examples\u002Fnode-fullstack\n",[202,215,217,220,224,227,230,233],{"class":204,"line":216},2,[202,218,219],{"class":208},"mm",[202,221,223],{"class":222},"sbRf1"," --runtime",[202,225,226],{"class":212}," docker-compose",[202,228,229],{"class":212}," dev",[202,231,232],{"class":212}," setup",[202,234,235],{"class":222}," --services\n",[202,237,239,241,244,247,250],{"class":204,"line":238},3,[202,240,219],{"class":208},[202,242,243],{"class":212}," target",[202,245,246],{"class":212}," preference",[202,248,249],{"class":212}," set-runtime",[202,251,252],{"class":212}," docker-compose\n",[202,254,256,258],{"class":204,"line":255},4,[202,257,219],{"class":208},[202,259,260],{"class":212}," doctor\n",[145,262,263,264,172],{},"Setup creates a local target, installs a local certificate authority, and asks\nyou to approve the shared HTTPS proxy, dashboard, and Mailpit services. Linux\ncertificate trust and ports 80\u002F443 can require ",[149,265,266],{},"sudo",[145,268,269,270,273],{},"The saved runtime preference makes later commands use Docker Compose. If you\nprefer Podman Compose, use ",[149,271,272],{},"podman-compose"," in setup and the preference command;\nDocker Buildx is still needed for builds.",[145,275,276,277,279,280,283],{},"On an existing checkout, bare ",[149,278,219],{}," also offers guided setup for missing local\nprerequisites. ",[149,281,282],{},"mm doctor"," lists independent blockers together; at this stage,\na missing published image is expected until the build step below. Neither\ncommand publishes images without a reviewed build.",[154,285,287],{"id":286},"configure-the-image-repositories","Configure the image repositories",[145,289,290,291,294,295,298],{},"Open ",[149,292,293],{},".config\u002Fmm.yaml"," in this example directory. Replace the two\n",[149,296,297],{},"modules.*.build.image"," values with repositories you own:",[162,300,301,306],{},[165,302,303],{},[149,304,305],{},"ghcr.io\u002Fexample\u002Fmm-node-fullstack-frontend",[165,307,308],{},[149,309,310],{},"ghcr.io\u002Fexample\u002Fmm-node-fullstack-backend",[145,312,313],{},"Commit those changes before building. Publication requires a clean worktree\nand complete Git history and tags. Check both:",[193,315,317],{"className":195,"code":316,"language":197,"meta":198,"style":198},"git status --short\ngit rev-parse --is-shallow-repository\n",[149,318,319,330],{"__ignoreMap":198},[202,320,321,324,327],{"class":204,"line":205},[202,322,323],{"class":208},"git",[202,325,326],{"class":212}," status",[202,328,329],{"class":222}," --short\n",[202,331,332,334,337],{"class":204,"line":216},[202,333,323],{"class":208},[202,335,336],{"class":212}," rev-parse",[202,338,339],{"class":222}," --is-shallow-repository\n",[145,341,342,343,346,347,350,351,172],{},"The first command should print nothing; the second should print ",[149,344,345],{},"false",". For a\nshallow clone, run ",[149,348,349],{},"git fetch --unshallow --tags",". Otherwise fetch missing tags\nwith ",[149,352,353],{},"git fetch --tags",[154,355,357],{"id":356},"prepare-the-environment","Prepare the environment",[193,359,361],{"className":195,"code":360,"language":197,"meta":198,"style":198},"mm config validate\nmm secret init age\nmm secret generate --missing\nmm env check\n",[149,362,363,373,386,398],{"__ignoreMap":198},[202,364,365,367,370],{"class":204,"line":205},[202,366,219],{"class":208},[202,368,369],{"class":212}," config",[202,371,372],{"class":212}," validate\n",[202,374,375,377,380,383],{"class":204,"line":216},[202,376,219],{"class":208},[202,378,379],{"class":212}," secret",[202,381,382],{"class":212}," init",[202,384,385],{"class":212}," age\n",[202,387,388,390,392,395],{"class":204,"line":238},[202,389,219],{"class":208},[202,391,379],{"class":212},[202,393,394],{"class":212}," generate",[202,396,397],{"class":222}," --missing\n",[202,399,400,402,405],{"class":204,"line":255},[202,401,219],{"class":208},[202,403,404],{"class":212}," env",[202,406,407],{"class":212}," check\n",[145,409,410,411,414],{},"The example declares a generated MongoDB password. Age is the encrypted local\nsecret store used here. ",[149,412,413],{},"mm env check"," should finish without missing values;\nthe password is not written into project configuration.",[154,416,12],{"id":417},"build-and-deploy",[145,419,420,421,424],{},"Run the plan command first. Review its output, then replace ",[149,422,423],{},"BUILD_PLAN_HASH","\nwith the hash it prints:",[193,426,428],{"className":195,"code":427,"language":197,"meta":198,"style":198},"mm build --publish plan\nmm build --publish apply --plan BUILD_PLAN_HASH --yes\n",[149,429,430,443],{"__ignoreMap":198},[202,431,432,434,437,440],{"class":204,"line":205},[202,433,219],{"class":208},[202,435,436],{"class":212}," build",[202,438,439],{"class":222}," --publish",[202,441,442],{"class":212}," plan\n",[202,444,445,447,449,451,454,457,460],{"class":204,"line":216},[202,446,219],{"class":208},[202,448,436],{"class":212},[202,450,439],{"class":222},[202,452,453],{"class":212}," apply",[202,455,456],{"class":222}," --plan",[202,458,459],{"class":212}," BUILD_PLAN_HASH",[202,461,462],{"class":222}," --yes\n",[145,464,465],{},"After the build succeeds, do the same for deployment:",[193,467,469],{"className":195,"code":468,"language":197,"meta":198,"style":198},"mm deploy plan\nmm deploy apply --plan DEPLOY_PLAN_HASH --yes\nmm service status\n",[149,470,471,480,495],{"__ignoreMap":198},[202,472,473,475,478],{"class":204,"line":205},[202,474,219],{"class":208},[202,476,477],{"class":212}," deploy",[202,479,442],{"class":212},[202,481,482,484,486,488,490,493],{"class":204,"line":216},[202,483,219],{"class":208},[202,485,477],{"class":212},[202,487,453],{"class":212},[202,489,456],{"class":222},[202,491,492],{"class":212}," DEPLOY_PLAN_HASH",[202,494,462],{"class":222},[202,496,497,499,502],{"class":204,"line":238},[202,498,219],{"class":208},[202,500,501],{"class":212}," service",[202,503,504],{"class":212}," status\n",[145,506,507],{},"The frontend, backend, and MongoDB should now be running. Check the reported\nhealth before continuing. If a plan becomes stale, generate a new one rather\nthan reusing its hash.",[154,509,511],{"id":510},"check-the-api","Check the API",[145,513,514],{},"This command tests HTTPS without changing your machine's DNS:",[193,516,518],{"className":195,"code":517,"language":197,"meta":198,"style":198},"curl --resolve backend.node-fullstack.test:443:127.0.0.1 \\\n  https:\u002F\u002Fbackend.node-fullstack.test\u002Fhealth\n",[149,519,520,535],{"__ignoreMap":198},[202,521,522,525,528,531],{"class":204,"line":205},[202,523,524],{"class":208},"curl",[202,526,527],{"class":222}," --resolve",[202,529,530],{"class":212}," backend.node-fullstack.test:443:127.0.0.1",[202,532,534],{"class":533},"s1Wbj"," \\\n",[202,536,537],{"class":204,"line":216},[202,538,539],{"class":212},"  https:\u002F\u002Fbackend.node-fullstack.test\u002Fhealth\n",[145,541,542,543,546],{},"Expect ",[149,544,545],{},"{\"status\":\"ok\"}",". If it fails, use:",[193,548,550],{"className":195,"code":549,"language":197,"meta":198,"style":198},"mm service logs backend --tail 50\nmm doctor\n",[149,551,552,570],{"__ignoreMap":198},[202,553,554,556,558,561,564,567],{"class":204,"line":205},[202,555,219],{"class":208},[202,557,501],{"class":212},[202,559,560],{"class":212}," logs",[202,562,563],{"class":212}," backend",[202,565,566],{"class":222}," --tail",[202,568,569],{"class":533}," 50\n",[202,571,572,574],{"class":204,"line":216},[202,573,219],{"class":208},[202,575,260],{"class":212},[145,577,578,579,582,583,586,587,590,591,594,595,598,599,602,603,607],{},"After installer workstation setup, wildcard ",[149,580,581],{},".test"," resolution is already\nconfigured. Open ",[149,584,585],{},"https:\u002F\u002Ffrontend.node-fullstack.test",". The frontend and backend\nnames are generated automatically from their service names and ",[149,588,589],{},"project.name",";\nyou do not need to add ",[149,592,593],{},"domains"," entries or edit your hosts file. If you installed\nwith ",[149,596,597],{},"--no-setup",", run ",[149,600,601],{},".\u002Finstall.sh --setup-only"," from the mm checkout first.\nSee ",[168,604,606],{"href":605},"..\u002Flocal-https","local HTTPS"," for certificate and DNS troubleshooting.",[145,609,610,611,615],{},"The ",[168,612,614],{"href":613},".\u002Fnode-fullstack","Node example guide"," adds a mail test, source editing,\nand dashboard links.",[154,617,619],{"id":618},"clean-up","Clean up",[145,621,622],{},"Stop the project's development stacks:",[193,624,626],{"className":195,"code":625,"language":197,"meta":198,"style":198},"mm dev down --yes\n",[149,627,628],{"__ignoreMap":198},[202,629,630,632,634,637],{"class":204,"line":205},[202,631,219],{"class":208},[202,633,229],{"class":212},[202,635,636],{"class":212}," down",[202,638,462],{"class":222},[145,640,641],{},"Shared services are separate. If no other projects need them, review removal:",[193,643,645],{"className":195,"code":644,"language":197,"meta":198,"style":198},"mm --runtime docker-compose dev services plan --operation remove\nmm --runtime docker-compose dev services remove --plan SERVICES_PLAN_HASH --yes\n",[149,646,647,669],{"__ignoreMap":198},[202,648,649,651,653,655,657,660,663,666],{"class":204,"line":205},[202,650,219],{"class":208},[202,652,223],{"class":222},[202,654,226],{"class":212},[202,656,229],{"class":212},[202,658,659],{"class":212}," services",[202,661,662],{"class":212}," plan",[202,664,665],{"class":222}," --operation",[202,667,668],{"class":212}," remove\n",[202,670,671,673,675,677,679,681,684,686,689],{"class":204,"line":216},[202,672,219],{"class":208},[202,674,223],{"class":222},[202,676,226],{"class":212},[202,678,229],{"class":212},[202,680,659],{"class":212},[202,682,683],{"class":212}," remove",[202,685,456],{"class":222},[202,687,688],{"class":212}," SERVICES_PLAN_HASH",[202,690,462],{"class":222},[145,692,693,694,697,698,701,702,705],{},"Replace ",[149,695,696],{},"SERVICES_PLAN_HASH"," with the removal plan's hash. Retained data is\npreserved unless you add ",[149,699,700],{},"--purge"," to both service commands. Purging deletes\nMailpit and access-gate data. Use ",[149,703,704],{},"mm dev tls uninstall"," only when you also want\nto remove the installation's local certificate trust.",[707,708,709],"style",{},"html pre.shiki code .svrjQ, html code.shiki .svrjQ{--shiki-default:var(--mm-code-function)}html pre.shiki code .sv8ni, html code.shiki .sv8ni{--shiki-default:var(--mm-code-string)}html pre.shiki code .sbRf1, html code.shiki .sbRf1{--shiki-default:var(--mm-code-option)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s1Wbj, html code.shiki .s1Wbj{--shiki-default:var(--mm-code-constant)}",{"title":198,"searchDepth":216,"depth":216,"links":711},[712,713,714,715,716,717,718],{"id":156,"depth":216,"text":157},{"id":187,"depth":216,"text":188},{"id":286,"depth":216,"text":287},{"id":356,"depth":216,"text":357},{"id":417,"depth":216,"text":12},{"id":510,"depth":216,"text":511},{"id":618,"depth":216,"text":619},"Run the Node frontend, API, and MongoDB example with local HTTPS.","md",{},true,{"title":129,"description":719},"docs\u002Ftutorials\u002Ffirst-local-app","jDSGrqujaygpL9nL9LsdChOz1T6tEn4pC5rr3k_z-HI",1794512545910]