A practical deployment guide — Last updated: September 2026
Software deployment is the process of taking working source code and making it available to users on a server. It is the step that turns a project on your machine into a service other people can reach. This guide walks through the standard deployment workflow — from a local project to a running production service — and explains what each step does and why it matters.
This is not a tutorial for any specific cloud provider or platform. The concepts here apply whether you are deploying to a virtual machine, a container platform, a platform-as-a-service, or a custom infrastructure. The goal is to understand the workflow well enough to recognize what is happening when a deployment fails — and to know how to fix it.
You can practice every step in this guide using the deployment simulator at LearnToDeploy. The simulator runs entirely in your browser — nothing is deployed to real servers, but the failure modes are real.
Before anything can be deployed, the source code has to become something that can run on a server. For a compiled language, that means compiling. For a interpreted language, it may mean installing dependencies and bundling assets. For a front-end application, it means producing static files.
A build is more than a transformation — it is a gate. A proper build catches problems before they reach users: syntax errors, missing dependencies, type mismatches, failing tests. The build step exists so that broken code fails here, not in production.
package-lock.json, requirements.txt,
go.sum, Cargo.lock).
An application rarely runs the same way in every environment. Development, staging, and production usually differ in database connections, API endpoints, ports, log levels, and secret keys. Environment configuration is how you make the same application behave correctly in each place without changing the code.
The standard approach is environment variables — key-value pairs injected at runtime. The application reads them when it starts and uses them to configure database connections, ports, feature flags, and secrets.
DATABASE_URL or DATABASE_HOST / DATABASE_PORT — where the app
connects to its database.PORT — which port the application listens on.LOG_LEVEL — how verbose the application logs are.ENV or NODE_ENV — which runtime profile to use (development, staging, production).
A .env.example file listing every environment variable the application expects — with dummy values
or comments explaining what each one does — is one of the most useful things a deployable project can include.
It tells anyone setting up the environment exactly what they need.
# .env.example PORT=8000 DATABASE_HOST=localhost DATABASE_PORT=5432 DATABASE_NAME=myapp LOG_LEVEL=info
Setting environment variables correctly is its own step because it is where so many deployments fail. The variables need to be present in the environment where the application runs — not just on your local machine. On a platform, this usually means configuring them in the platform's dashboard, a secrets manager, or a deployment configuration file.
DATABASE_HOST and it is
missing, the app should fail at startup with a clear error — not silently default to something wrong.localhost. The production port is whatever the platform expects, not whatever your development
server uses.
.env files belong in .gitignore. Real
secrets come from a secrets manager or platform configuration, never from source control.PORT variable and the application's configured
port must match.# Running the app with environment variables PORT=8000 DATABASE_HOST=postgres DATABASE_PORT=5432 \ LOG_LEVEL=info node server.js
After the build step, you have something deployable — a compiled binary, a Docker image, a bundle of static files, or a directory of installed dependencies and source code. This artifact is what you ship. It should be the same artifact you tested, not a rebuild from different source.
Versioning the artifact matters. If you deploy myapp:latest and something goes wrong, you need to
know what :latest was at that moment — and you need to be able to deploy the previous version.
Tagging images and binaries with a version, a git commit hash, or a build number makes deployments traceable.
# Building a Docker image with a version tag docker build -t myapp:1.2.0 . docker tag myapp:1.2.0 myapp:latest
An artifact should be immutable — once built, it does not change. If you need to change something, you build a new artifact with a new version. This is what makes rollbacks possible: you do not rebuild the old version, you redeploy the one you already have.
Docker containers package an application with its runtime, dependencies, and configuration into a single image that runs the same way on any machine with a container runtime. Containerization is not required for every deployment, but it is the standard for a reason: it eliminates the “it works on my machine” problem by making the deployment environment explicit and reproducible.
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 8000 CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
python:3.12-slim is reproducible.
python:latest is not — it changes over time and can break your build.
CMD or ENTRYPOINT, the
container exits immediately.EXPOSE instruction
documents the port; the application must actually listen on it.Continuous integration and continuous deployment (CI/CD) automate the path from source code to production. CI runs the build and tests on every change. CD takes the built artifact and deploys it — sometimes automatically, sometimes after a manual approval.
CI/CD is not just about speed — it is about consistency. A manual deployment that depends on someone remembering every step is a deployment that will eventually go wrong. A pipeline that runs the same steps in the same order every time is a deployment you can trust.
The deployment simulator includes CI/CD tooling — a pipeline editor, build stages, and deployment logs — so you can practice configuring and debugging pipelines without a real CI system.
Deployment is the act of making the artifact available to users. What this looks like depends on the platform: pushing a container image and restarting a service, uploading static files to a CDN, or triggering a platform to pull the latest image and roll it out.
A deployment is not successful when the command returns successfully. It is successful when the application is running and responding to requests. The deployment step should include a verification that the service is actually alive — usually a health check.
Once the deployment is live, monitoring tells you whether it is healthy. Monitoring is not just about alerts — it is about having enough visibility into the running service to notice problems before users do.
A deployment that you cannot monitor is a deployment you cannot operate. Make sure the service emits enough signal — health checks, structured logs, metrics — to understand its state.
When something goes wrong, logs are the first place to look. Logs record what the application and the platform did — startup, configuration, requests, errors, shutdown. Reading logs is a core debugging skill.
Deployment failures fall into a few common categories. Knowing the category tells you where to look.
The troubleshooting process is the same regardless of the specific failure: read the logs, check the configuration, verify the state of the running service, form a hypothesis, test it, and fix the root cause — not just the symptom.
When a deployment causes a problem, the fastest way to restore service is often to go back to the last known-good version. This is a rollback — redeploying the previous artifact rather than trying to fix the current one live.
Either way, the goal is to restore service first and understand the problem second. A postmortem after the incident captures what happened, why, and what to do differently next time.
Reading about deployment is not the same as doing it. The LearnToDeploy deployment simulator lets you practice this entire workflow in a browser-based environment:
The simulator includes deployment failures planted in the projects — wrong ports, missing environment variables, broken Dockerfiles, failed health checks. Each one is a realistic problem that teaches you how to diagnose and fix it. Nothing is deployed to real infrastructure; everything is learned in the browser.
For a broader view of the concepts behind each step, see the deployment concepts library inside the app.