Docker & compose
Prerequisites
- Docker and Docker Compose
1. Create a directory for your deployment
mkdir my-links-deployment
cd my-links-deployment2. Download the compose file and the environment template
curl -fsSLO https://raw.githubusercontent.com/my-links/my-links/main/compose.image.yml
curl -fsSL https://raw.githubusercontent.com/my-links/my-links/main/.env.example -o .envcompose.image.yml runs the published image next to a PostgreSQL container, and reads its settings from the .env next to it.
The PostgreSQL port is bound to 127.0.0.1 on purpose: the database is reachable from the host itself, never from the network. The application reaches it over the compose network and always uses port 5432 there, whatever DB_PORT is.
favicon-volume holds the cached favicon images (see Configuration). Unlike postgres-volume, losing it isn't a data loss (the app re-downloads what it needs), but keeping it out of your backup strategy means a restore re-fetches every favicon from scratch instead of serving them immediately.
/app/storage/favicons must be a persistent volume. compose.image.yml already declares it, but platforms that build only the Dockerfile (Dokploy in "Application" mode, Coolify, a plain docker run) do not read it: add the mount yourself, for example -v favicon-volume:/app/storage/favicons. Without it, every redeploy wipes the stored icons while their database rows survive. The app re-downloads a missing icon the next time it is requested, but until then each affected link shows its placeholder.
x-logging caps what Docker keeps of each container's output at three 10 MB files, the oldest dropped first. Docker's default keeps everything for as long as the container exists. Platforms that do not read the compose file need the same limit set on their side, for example --log-opt max-size=10m --log-opt max-file=3 on docker run. See Logging for what the log contains.
3. Edit your .env
The .env you downloaded is the template. Set at least APP_KEY, APP_URL, DB_USER, DB_PASSWORD and DB_DATABASE; see Configuration for what each variable does.
Sign-up is closed by default: only the setup link printed in the logs on first boot can create the first account, and /register closes once it exists. To let anyone register, set ALLOW_REGISTRATION=open, see Registration.
4. Start it
docker compose -f compose.image.yml up -dThis pulls the image from Docker Hub, starts PostgreSQL, applies database migrations automatically, and starts the application in production mode, reachable on the port set in PORT (default 3333). Account maintenance runs alongside it in the same container with no separate setup, see Maintenance.
5. Create the first administrator
While no administrator exists, the logs print a setup link. Open it and register, the form carries the token for you:
docker compose -f compose.image.yml logs my-links | grep setup_tokenThe link is <APP_URL>/register?setup_token=<token>. See First administrator.
Native deployment
If you would rather run it without Docker:
Node.js, pnpm and PostgreSQL 18 installed and running. The Node.js and pnpm versions are pinned in
mise.toml, andmise installsets them up.Clone and install:
bashgit clone https://github.com/my-links/my-links.git cd my-links pnpm installCopy
.env.exampleto.envat the repository root and configure it:bashcp .env.example .envPoint
DB_*at your PostgreSQL instance, then apply migrations. Everynode acecommand runs fromapps/webappwithENV_PATH=../.., see Configuration:bashcd apps/webapp ENV_PATH=../.. node ace migration:runCompile the translation catalogs and build:
bashENV_PATH=../.. pnpm run compile ENV_PATH=../.. node ace buildInstall the production dependencies next to the build output. From the repository root, this resolves a self-contained
node_modulesin a temporary directory (the same way the Docker image does) and copies it intobuild/:bashcd ../.. pnpm --filter @my-links/webapp deploy --prod --ignore-scripts --legacy /tmp/my-links-deploy cp -r /tmp/my-links-deploy/node_modules apps/webapp/build/Copy the root
.envinto the build output (the built app readsbuild/.env, not the root one) and start it:bashcp .env apps/webapp/build/ cd apps/webapp/build pnpm run start
The application is reachable on the port set in PORT.
Account maintenance and the other sweeps are scheduled inside the application itself, on Docker and on a native pnpm run start alike. A system cron is only needed if you disable the scheduler preload or run the application without the web process, see Maintenance for the commands to run from build/.